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

Android Training Program - Portugal, Aula 4

ATP Portugal
November 11, 2020

Android Training Program - Portugal, Aula 4

Aula #4: Fundações III 💪

Para a melhor experiência possível queremos que a nossa interface seja o mais rápida e fluida possível - para isso, operações pesadas devem ser delegadas para threads secundárias.

- Architecture Components
- Operações assíncronas
- Live data
- ViewModel
- Permissões

ATP Portugal

November 11, 2020
Tweet

More Decks by ATP Portugal

Other Decks in Education

Transcript

  1. • Sejam excelentes uns para os outros • Fale mais

    alto se vir ou ouvir alguma coisa • O assédio não é tolerado • Pratique "Sim e" um ao outro Código de conduta Mais informações: http://bit.ly/2IhF0l3
  2. Andres-Leonardo Martinez-Ortiz Google Carlos Mota Formador Renato Almeida Formador @davilagrau

    @cafonsomota @tallnato Equipa Daniela Ferreira Gestora de comunidades
  3. • 12 aulas • 1h30 cada aula • ~1 aula

    por semana • 14 Outubro a 16 Dezembro • YouTube live • Suporte assíncrono contínuo via Discord/email • Todo o código disponível no GitHub Photo by Arif Riyanto on Unspla O programa
  4. #0 14 de Outubro Pronto para começar #1 21 de

    Outubro Bem-vindos ao Android #2 28 de Outubro Fundações I #3 04 de Novembro Fundações II #4 11 de Novembro Fundações III #5 18 de Novembro Listas, listas e mais listas #6 25 de Novembro Jetpack, Jetpack, Jetpack! #7 - #8 02 - 03 de Dezembro Firebase #9 - #10 09 - 10 de Dezembro MLKit & TensorFlow #11 16 de Dezembro Resumo Semana Semana Calendário ✅ ✅ ✅ Direto ✅
  5. Photo by Mollie Sivaram on Unsplash ‍ ~5000 participantes ~2600

    subscrições no YouTube ~20000 visualizações Obrigado! ‍♀
  6. Sumário Photo by Mika Baumeister on Unsplash • Resumo da

    aula anterior • ViewModel • LiveData • Operações assíncronas • Permissões • Kotlin para principiantes • Castanhas quentes e boas!
  7. Resolução medium resolution (mdpi) HTC Wildfire S high resolution (hdpi)

    Samsung Galaxy S2 extra extra extra high resolution (xxxhdpi) Pixel 5
  8. Trabalho para casa • Melhora a UI do ecrã inicial

    • Adicionar uma ScrollView • Enviar o nome de utilizador para a Activity principal • Utilizar o botão de ação do teclado para validar
  9. ViewModel A classe ViewModel foi desenhada para guardar e gerir

    dados relacionados com a interface do utilizador de forma consciente em relação ao ciclo de vida. Esta classe permite que os dados continuem disponíveis mesmo quando as configurações são alteradas. Por exemplo, quando o ecrã é rodado e a activity é reconstruída. - Android Developers documentation
  10. • Permite separar os dados da sua representação gráfica •

    Está associado a uma Activity ou Fragment • Continua a ser executado mesmo que a aplicação não esteja visível • Permanece em memória quando uma Activity é reconstruída • Não deve aceder à interface gráfica da aplicação ViewModel
  11. Ciclo de vida Activity onCreate onResume onPause onDestroy ecrã rodado

    onCreate onResume activity recriada finish() onPause onDestroy activity destruída ViewModel onCleared() Activity activity criada
  12. class CounterViewModel : ViewModel() { var number: Int = 0

    private set fun increment() { ++number } } Como utilizar? Lógica no ViewModel CounterViewModel.kt
  13. class CounterViewModel : ViewModel() { var number: Int = 0

    private set fun increment() { ++number } } Como utilizar? Lógica no ViewModel não é possível alterar o valor de number, fora desta class CounterViewModel.kt
  14. class CounterActivity : AppCompatActivity() { lateinit var viewModel : CounterViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_counter) viewModel = ViewModelProvider(this).get(CounterViewModel::class.java) } } Como utilizar? Instanciar numa Activity CounterActivity.kt
  15. class CounterActivity : AppCompatActivity() { lateinit var viewModel : CounterViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_counter) viewModel = ViewModelProvider(this).get(CounterViewModel::class.java) } } Como utilizar? Instanciar numa Activity CounterActivity.kt
  16. class CounterActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?)

    { ... findViewById<Button>(R.id.btn_counter).setOnClickListener { viewModel.increment() Toast.makeText(this, "Number: ${viewModel.number}", LENGTH_SHORT).show() } } } Como utilizar? Lógica na Activity CounterActivity.kt
  17. class CounterActivity : AppCompatActivity() { ... override fun onCreate(savedInstanceState: Bundle?)

    { ... val tvCounter = findViewById<TextView>(R.id.tv_counter) findViewById<Button>(R.id.btn_counter).setOnClickListener { viewModel.increment() tvCounter.text = viewModel.number.toString() } } } CounterActivity.kt Como utilizar? Actualizar uma TextView
  18. class CounterViewModel : ViewModel() { var number: Int = 0

    private set fun increment() { ++number lotsOfProcessings(number) } ... } Bué processamento
  19. • Podemos ter um ViewModel partilhado, ◦ Permite comunicar entre

    uma Activity e um Fragment ◦ Permite comunicar entre vários Fragment’s E ainda...
  20. LiveData O LiveData é uma classe que contém dados observáveis.

    Ao contrário de uma classe observável normal, o LiveData tem em consideração o ciclo de vida, ou seja, respeita o ciclo dos componentes como Activities, Fragments ou Service’s. Esta consideração assegura que o LiveData só atualiza o componente que estão num estado ativo. - Android Developers documentation
  21. LiveData O LiveData é uma classe que contém dados observáveis.

    Ao contrário de uma classe observável normal, o LiveData tem em consideração o ciclo de vida, ou seja, respeita o ciclo dos componentes como Activities, Fragments ou Service’s. Esta consideração assegura que o LiveData só atualiza o componente que estão num estado ativo. - Android Developers documentation
  22. • Padrão de desenvolvimento de software • Um objeto (Observable)

    notifica os interessados (Observers) ◦ Quando este valor é alterado • Os interessados subscrevem para receber as atualizações Observável? Observable Observer Observer Observer ...
  23. • Assegura que a interface gráfica corresponde ao estado dos

    dados • Não tem memory leaks • Sem crashes por causa de Activities paradas • Sem manipulação manual do ciclo de vida • Sempre atualizado • Partilha de recursos Vantagens do LiveData
  24. class CounterViewModel : ViewModel() { var number: Int = 0

    private set fun increment() { ++number } } Como utilizar?
  25. class CounterViewModel : ViewModel() { private val _liveNumber = MutableLiveData<Int>()

    private var number: Int = 0 val liveNumber : LiveData<Int> = _liveNumber fun increment() { ++number _liveNumber.postValue(number) } } Como utilizar? No ViewModel
  26. class CounterViewModel : ViewModel() { private val _liveNumber = MutableLiveData<Int>()

    private var number: Int = 0 val liveNumber : LiveData<Int> = _liveNumber fun increment() { ++number _liveNumber.postValue(number) } } Como utilizar? No ViewModel
  27. class CounterViewModel : ViewModel() { private val _liveNumber = MutableLiveData<Int>()

    private var number: Int = 0 val liveNumber : LiveData<Int> = _liveNumber fun increment() { ++number _liveNumber.postValue(number) } } Como utilizar? No ViewModel
  28. class CounterActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    ... viewModel = ViewModelProvider(this).get(CounterViewModel::class.java) val tvCounter = findViewById<TextView>(R.id.tv_counter) findViewById<Button>(R.id.btn_counter).setOnClickListener { viewModel.increment() } viewModel.liveNumber.observe(this){ number -> tvCounter.text = number.toString() } } } Como utilizar? Na Activity
  29. class CounterActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    ... viewModel = ViewModelProvider(this).get(CounterViewModel::class.java) val tvCounter = findViewById<TextView>(R.id.tv_counter) findViewById<Button>(R.id.btn_counter).setOnClickListener { viewModel.increment() } viewModel.liveNumber.observe(this){ number -> tvCounter.text = number.toString() } } } Como utilizar? Na Activity
  30. class CounterActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    ... viewModel = ViewModelProvider(this).get(CounterViewModel::class.java) val tvCounter = findViewById<TextView>(R.id.tv_counter) findViewById<Button>(R.id.btn_counter).setOnClickListener { viewModel.increment() } viewModel.liveNumber.observe(this){ number -> tvCounter.text = number.toString() } } } Como utilizar? Na Activity
  31. • LiveData ◦ É uma classe abstrata, não permite atualizar

    o valor • MutableLiveData ◦ Classe que permite alterar o valor • Transformations.map ◦ Permite transformar o item • MediatorLiveData ◦ Permite juntar dois LiveData • Transformations.SwitchMap ◦ Utiliza o valor de um LiveData para produzir outro Tipos de LiveData
  32. atualizar atualizar atualizar atualizar desenha o ecrã desenha o ecrã

    desenha o ecrã objetivo: 60fps UI-thread Operações assíncronas
  33. atualizar operação pesada atualizar desenha o ecrã desenha o ecrã

    desenha o ecrã objetivo: 60fps UI-thread Operações assíncronas
  34. • A aplicação não é fluida ◦ Alguns frames podem

    não ser desenhados • ANR podem ser lançados pelo sistema ◦ ANR = Activity Not Responding • Má experiência para o utilizador Operações assíncronas Counter isn’t responding
  35. atualizar atualizar desenha o ecrã desenha o ecrã desenha o

    ecrã objetivo: 60fps operação pesada atualizar atualizar thread secundária Operações assíncronas thread de UI
  36. atualizar atualizar desenha o ecrã desenha o ecrã desenha o

    ecrã objetivo: 60fps thread de UI operação pesada atualizar atualizar Operações assíncronas progresso thread secundária
  37. • A aplicação fica muito mais fluida ◦ Não há

    perda de frames • Operações pesadas feitas numa thread secundária • Atualizações de UI são feitas na thread de UI ◦ UI = User Interface • O ecrã é atualizado cada 16 ms Operações assíncronas
  38. • Service • Threads • IntentService • AsyncTasks • WorkManager

    • JobScheduler • DownloadManager • AlarmManager • Coroutines Operações assíncronas Soluções
  39. • Service • Threads • IntentService • AsyncTasks • WorkManager

    • JobScheduler • DownloadManager • AlarmManager • Coroutines Operações assíncronas Soluções
  40. Operações assíncronas Soluções executam num momento exato downloads via HTTP

    requer a atenção do utilizador resultam de eventos do sistema DownloadManager ForegroundService WorkManager AlarmManager descarregar os materiais desta aula atualizar a caixa de entrada de emails ligar a uma rede WiFi o alarme a tocar que já são horas de acordar
  41. Allow Snapchat to access this device’s location? Ao instalar todas

    as permissões requisitadas são dadas Funcionalidades do sistemas precisam de autorização explícita do utilizador Android ... Android 6.0
  42. Allow Snapchat to access this device’s location? Ao instalar todas

    as permissões requisitadas são dadas Funcionalidades do sistemas precisam de autorização explícita do utilizador As permissões vão sendo cada vez mais restritivas: - Apenas enquanto estamos a utilizar a aplicação - Permitir apenas uma única vez Android ... Android 6.0 … Android 10
  43. Allow Snapchat to access this device’s location? Ao instalar todas

    as permissões requisitadas são dadas Funcionalidades do sistemas precisam de autorização explícita do utilizador As permissões vão sendo cada vez mais restritivas: - Apenas enquanto estamos a utilizar a aplicação - Permitir apenas uma única vez Android ... Android 6.0 … Android 10 Android 11
  44. Android 11 • Permissão única • O utilizador tem de

    permitir o acesso contínuo à localização ◦ Maior segurança ◦ Poupança de bateria • Revogação de permissões para aplicações que não são utilizadas
  45. Android 10+ • Permissão de leitura/escrita de ficheiros não é

    suficiente. • Ficheiro não criados pela aplicação que sejam modificados precisam de permissão adicional.
  46. val uri = MediaStore.setRequireOriginal(imageUri) contentResolver.openInputStream(uri).use { inputStream -> // ...

    } Aceder a um ficheiro E/AndroidRuntime: FATAL EXCEPTION: main Process: pt.atp.bobi, PID: 30411 java.lang.RuntimeException: Unable to start activity ComponentInfo{pt.atp.bobi/pt.atp.toy.CounterActivity}: java.io.FileNotFoundException: /storage/emulated/0/bobi.mp4: open failed: EACCES (Permission denied)
  47. val uri = MediaStore.setRequireOriginal(imageUri) contentResolver.openInputStream(uri).use { inputStream -> // ...

    } Aceder a um ficheiro E/AndroidRuntime: FATAL EXCEPTION: main Process: pt.atp.bobi, PID: 30411 java.lang.RuntimeException: Unable to start activity ComponentInfo{pt.atp.bobi/pt.atp.toy.CounterActivity}: java.io.FileNotFoundException: /storage/emulated/0/bobi.mp4: open failed: EACCES (Permission denied)
  48. override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String?>, grantResults: IntArray) { if

    (requestCode == REQUEST_CODE) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Acesso a tudo } else { // Ainda não dá ✋ } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults) } } Resultado
  49. • Pedir a permissão apenas quando o utilizador começa a

    interagir com a funcionalidade que necessita dessa permissão. • Não bloquear o utilizador. Dar a possibilidade ao utilizador de cancelar a permissão. • Se o utilizador rejeitar ou revogar a permissão que uma funcionalidade necessita, devemos permitir que o utilizador continue a utilizar a aplicação, possivelmente até a desativar a funcionalidade em questão. Algumas recomendações
  50. class kennel { val dog by lazy { Dog() }

    } Instanciação Lazy class Kennel { private Dog dog; public Dog getDog() { if(dog == null){ dog = new Dog(); } return dog; } }
  51. class Dog(val name:String, val color: String, val legs: int) val

    dog = Dog("Bobi", "Verde", 5) val (name, color, legs) = dog println(name) // Bobi println(color) // Verde println(legs) // 5 ‍♂ Deconstrução de uma variável
  52. val (name, color, legs) = dog if(legs > 4) {

    println("O $name, é um cão? ") } ‍♂ Deconstrução de uma variável
  53. val (name, _, legs) = dog if(legs > 4) {

    println("O $name, é um cão? ") } ‍♂ Deconstrução de uma variável
  54. val dogs = listOf<Dog>(...) for ((name, color, legs) in dogs)

    { // ... } ‍♂ Deconstrução de uma variável
  55. fun `O Bobi é lindo`() { ... } Espaços em

    nomes de funções ‍♂ ‍♂
  56. @Test fun `O Bobi é lindo`() { ... } Espaços

    em nomes de funções ‍♂ ‍♂ * Apenas aceitável utilizar isto em testes
  57. Destruir todas as Activities Para ativar: 1. Definições 2. Sistema

    3. Opções de programador 4. Não manter atividades
  58. Destruir todas as Activities Para ativar: 1. Definições 2. Sistema

    3. Opções de programador 4. Não manter atividades
  59. Android Studio Para criar uma pasta: 1. Botão direito na

    barra lateral 2. “New Project Group” Para mover um projeto: 1. Botão direito na barra lateral 2. “Move To Group”
  60. Android Studio Modificar as cores do logcat 1. Android Studio

    (barra no topo) 2. Preferences 3. Pesquisar por logcat 4. Selecionar o tipo e a cor
  61. adb Enviar texto para o telemóvel/emulador 1. Abrir o Terminal/Linha

    de comandos 2. adb shell input text “Isto é uma mensagem de texto com um emoji ” http://bit.ly/atp2020-codelabs
  62. adb Enviar texto para o telemóvel/emulador 1. Abrir o Terminal/Linha

    de comandos 2. adb shell input text “Isto é uma mensagem de texto com um emoji ” http://bit.ly/atp2020-codelabs