-
Notifications
You must be signed in to change notification settings - Fork 0
Sumarios
Miguel Gamboa edited this page Dec 18, 2019
·
34 revisions
Aulas:
- 09-09-2019 - Aula 01 - Apresentação
- 11-09-2019 - Aula 02 - Kotlin
- 16-09-2019 - Aula 03 e 04 - Kotlin - laboratório
- 23-09-2019 - Aula 05 - Android Intro
- 23-09-2019 - Aula 06 - Android LifeCycle
- 30-09-2019 - Aula 07 - Eventos e Listeners
- 30-09-2019 - Aula 08 - Custom Views
-
02-10-2019 - Aula 09 -
onSaveInstanceState
eSerializable
eParcelable
-
07-10-2019 - Aula 10 -
Parcelable
-
07-10-2019 - Aula 11 -
ViewModel
-
07-10-2019 - Aula 12 -
RecyclerView
,ViewHolder
eAdapter
- 14-10-2019 - Aula 13 e 14 - Laboratorio Trabalho 1
- 16-10-2019 - Aula 15 - Android HTTP
- 21-10-2019 - Aula 16 e 17 - Laboratorio Trabalho 1
-
23-10-2019 - Aula 18 -
ViewModelProvider.Factory
eApplication
-
28-10-2019 - Aula 19 -
LiveData
- 28-10-2019 - Aula 20 - Laboratorio Trabalho 1
- 30-10-2019 - Aula 21 - UI states e Threading model
- 04-11-2019 - Aula 22 e 23 - Avaliação do Trabalho 1
- 06-11-2019 - Aula 24 - Threading model e testes unitários
- 11-11-2019 - Aula 25 - Room e DAO
-
11-11-2019 - Aula 26 - Repository e
MediatorLiveData
- 13-11-2019 - Aula 27 - Laboratorio Trabalho 2
- 18-11-2019 - Aula 28 e 29 - Laboratorio Trabalho 2
- 20-11-2019 - Aula 30 - Laboratorio Trabalho 2
- 25-11-2019 - Aula 31 e 32 - Laboratorio Trabalho 2
- 27-11-2019 - Aula 33 - Laboratorio Trabalho 2
- 02-12-2019 - Aula 34 - Schedule background tasks
- 02-12-2019 - Aula 35 - Notificações
- 04-12-2019 - Aula 36 - Laboratorio Trabalho 3
- 09-12-2019 - Aula 37 e 38 - Laboratorio Trabalho 3
-
11-12-2019 - Aula 39 -
ContentProvider
,MediaStore
,MediaPlayer
and Permissions - 16-12-2019 - Aula 40 e 41 - Laboratorio Trabalho 3
-
18-12-2019 - Aula 42 - Foreground
Service
09-09-2019
- Apresentação.
- Âmbito da disciplina.
- Avaliação teórica e prática (3 entregas).
- Introdução ao ambiente de programação em Kotlin
- Compilador Kotlin de linha de comando (
kotlinc
) - Modelo de execução sobre a JVM
- Exercícios: Kotlin Koans
- Caracterização geral da linguagem Kotlin:
- Tipificação estática com inferência de tipos
- Object-Oriented com suporte para os estilos imperativo e funcional
- Funções:
fun [<parametros de tipo>] <nome> ([parametros formais]) : [tipo retorno] {...}
- Tipo Função -- notação especial correspondente à assinatura, i.e.
(Tipo de Parametros)
->
Tipo Retorno
- Tipo Função e.g.
(Int) -> String
,() -> Unit
, entre outros. -
Typealias e.g.
Predicate<T> = (T) -> Boolean
-
lambdas:
{ param1, param2, ... -> block }
or{ block }
-
it
-- implicit lambda parameter -
Function references -
::
like Java - SAM (Single Abstract Method) compatível com lambda
-
read-only (e.g.
listOf()
,setOf
)<vs>
mutable collections (e.g.mutableListOf
,mutableSetOf
)
-
?
for Nullable --val variable: Type? = value
-
?.
-- safe call -- access member only if notnull
. Otherwise returns null.
- Resolução do TPC 2
- Basic parts: Manifest, Main Activity e Gradle build;
-
Activity
: UI Component (subclasse deContext
) -
Activity
:- visual + comportamento
- visual (
src/main/res/layout/...xml
) + comportamento (src/main/java
) - Analogia ao front-end Web: visual (HTML e CSS) + comportamento (Javascript)
-
Activity
-- ciclo de vida, i.e.Created
,Started
(visível),Resumed
(primeiro plano),Paused
,Stopped
, etc; -
Activity
-- métodos "gancho", i.e.onCreated()
,onStarted()
, etc
-
Actvities sao iniciadas por instâncias de
Intent
-
Intent
:- mensagem assíncrona;
- ligação entre componentes (e.g. Activities)
-
Main Activity
<--
intent.action.MAIN
- UI = Layouts + Widgets:
-
Layouts =
ViewGroup
objects = widget containers -
Widgets =
View objects
= UI components e.g. botões, caixas de texto, etc
-
Layouts =
ConstraintLayout
-
Android Studio Layout Editor
--->
activity_...xml
-
R
- classe gerada dinamicamente com constantes dos identificadores (e.g.R.id.buttonSend
)
- Eventos e Listeners --
view.setOnClickListener(View -> Unit)
findViewById(@IdRes int id)
-
Intent
- representa uma mensagem assíncrona; ligação entre componentes (e.g. Activities)- e.g.
Intent(this, DisplayMessageActivity::class.java)
: -
Exlicit: identifica o tipo da actividade a ser instanciada (
DisplayMessageActivity::class.java
) -
putExtra(key, value)
egetStringExtra(key)
- e.g.
startActivity(intent)
-
Android Manifest:
android:parentActivityName
-->
navegação -
Activity
: ciclo de vida -
demo: logging lifecycle state transitions e.g.
override fun onStart(){...}
- Intercalação entre estados de activities da mesma App.
-
Lifecycle-Aware Components :
-
LifecycleOwner
---->
LifecycleObserver
myLifecycleOwner.getLifecycle().addObserver(MyObserver())
- e.g.
@OnLifecycleEvent(Lifecycle.Event.ON_START)
-
-
Custom Views:
constructor(context: Context, attrs: AttributeSet)
-
custom drawing:
onDraw(canvas: Canvas)
ePaint
-
interaction:
override fun onTouchEvent(event: MotionEvent)
- Concepção da app
Sketcher
:-
SketcherView
--->*
Line
--->*
XyPair
-
- Implementação de
SketcherView
:
val lines : MutableList<Line> = mutableListOf()
var curr : Line? = null
override fun onDraw(canvas: Canvas) = lines.forEach { it.draw(canvas) }
override fun onTouchEvent(event: MotionEvent): Boolean { ... }
- TPC: Identificar a instrução em falta para que a UI seja actualizada em resposta da interacção com utilizador.
-
Activity
--onSaveInstanceState(Bundle)
eonRestoreInstanceState(Bundle)
-
Bundle
:- Pares chave--valor
-
put<Primitive>
ouput<Primitive>Array
- Instâncias de tipos complexos
=>
Serializable
!!! Atenção aos custos !!!- Alternativa
Parcelable
- Alternativa
- Implementar
Serializable
emLine
eXyPair
-
View
--onSaveInstanceState(): Parcelable
eonRestoreInstanceState(state: Parcelable)
- Implementar
Parcelable
emLine
eXyPair
:writeToParcel(dest: Parcel) : T
-
Parcelable.Creator<T>
::
createFromParcel(source: Parcel) : T
-
Parcelable.Creator<T>
::
newArray(int size) : Array<T>
-
TPC: Completar a implementação de
Parcelable
emLine
. SubstituirSerializable
porParcelable
emon<Save|Restores>InstanceState
- Implementação de
Parcelable
- Evitar a criação de um array intermédio.
- Teste unitário para a implementação de
Parcelable
- !!! Problema: obter uma instância real de
Parcel
? (depende da infra-estrutura Android) - Robolectric framework -- ambiente Android para os testes unitários.
androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0
- Adicionar
ViewModel
a sketcher app:class SketcherModel : ViewModel()
- Mover
lines
paraSketcherViewModel
- Transformação do desenho da aplicação
Sketcher
paraViewModel
:-
MainActivity
--->1
SketcherView
---->*
Line
---->*
XyPair
-
MainActivity
--->1
SketcherView
--->1
SketcherViewModel
---->*
Line
---->*
XyPair
-
-
SketcherView
--->1
SketcherViewModel
através de:ViewModelProviders.of(ctx)[SketcherModel::class.java]
- Instância de
ViewModel
é gerida pela infra-estrutura.
-
RecyclerView
-- 1. scrolling list, 2. large data sets, 3. data that frequently changes - Dependência gradle:
'androidx.recyclerview:recyclerview:1.0.0'
-
RecyclerView
:-
view holder object:
- Um objecto por cada elemento visualizado
- Instância de
RecyclerView.ViewHolder
- São instanciados apenas o número de view holders necessários à UI
-
view holder objects -- geridos por adapters -- instâncias de
RecyclerView.Adapter
. -
adapter -- liga (binds) view holders aos seus dados
--->
onBindViewHolder()
-
view holder object:
- Imlementação de
RecyclerView.Adapter
:class <Name> : RecyclerView.Adapter<ViewHolder_Name>()
-
override fun getItemCount()
-- número de elementos na fonte de dados -
override fun onBindViewHolder(holder ...)
-- atribui dados aoholder
-
override fun onCreateViewHolder(parent ... )
:- Obtém a
View
correspondente a um layout -
Inflate parent com a
View
do ponto 1. - Instancia e retorna um novo
ViewHolder
- Obtém a
- Implementação da App GeniuZ para apresentação de informações da Last.fm Web API.
- Utilização de
RecyclerView
com implementação deArtistAdapter
para dados só em memória (i.e.Array<ArtistDto>
) -
LayoutManager
eLinearLayoutManager
- Android HTTP framework envolve várias acções:
- Construção de um pedido HTTP
- Execução
- Obtenção da resposta
-
Parsing headers e body e.g. JSON
=>
Objecto - Actualizar a UI.
- Problema: operações de IO, parsing ou qq trabalho de background não pode ocupar a UI thread
- App Resources
- Tipos de recursos por pasta, e.g.
layout
,values
, etc. - Recursos alternativos
res/<resource_name>-<qualifier>
e.g.res/values-pt/strings.xml
- App GeniuZ simulação de obtenção dos dados sobre
LastfmWebApiMock
RecyclerView.Adapter::notifyDataSetChanged()
- Introduzir
ViewModel
para manutenção de estado em reconfiguração
- Problema: operações de IO, parsing ou qq trabalho de background não pode ocupar a UI thread
- Observar o resultado de executar IO na main thread:
- Exemplo: pedido HTTP bloqueante via
URL
(LastfmWebApiBlocking
) - NetworkOnMainThreadException
- Exemplo: pedido HTTP bloqueante via
- Distinção entre idioma Sync versus Async:
-
Sync: Resultado
=
Retorno do método=>
Conclusão da execução do método -
Async: Resultado
!=
Retorno do método
-
Sync: Resultado
'com.android.volley:volley:1.1.1'
-
Volley - biblioteca HTPT para Android:
-
RequestQueue
---->*
Request
- Gestão de worker threads
- Entrega a resposta de volta à main thread (
=>
actualização safe da UI)
-
Volley.newRequestQueue(context)
queue.add(request)
StringRequest(Request.Method,<url>,Response.Listener<String>,Response.ErrorListener)
-
AndroidManifest.xml
:<uses-permission android:name="android.permission.INTERNET" />
- Implementação de
LastfmWebApi
via Volley com API baseada em callbacks
- !!!! Problema: Reconfiguração gera novo pedido HTTP na
AlbumsActivity
-
Refactoring: mover a propriedade
LastfmWebApi
daActivity
para oViewModel
-
Activity
1 -----> 1
ViewModel
-
Activity
-->
ViewModel
-->
LastfmWebApi
-
=>
ArtistsViewModel
passa a ser instanciado por umViewModelProvider.Factory
- e.g.
LasftfmViewModelProviderFactory
- e.g.
-
Application
-- Classe base para manter o estado global da aplicação.- e.g
GeniuzApp
-- para manter uma instância deLastfmWebApi
- Manifesto:
<application android:name=".GeniuzApp"
... />`
- e.g
ViewModelProviders.of(this, LasftfmViewModelProviderFactory(application as GeniuzApp))
- !!!!! Problema: Processamento demorado do pedido HTTP na UI Thread impede a sua actualização (e.g. reconfiguração)
-
Asynchronous Task
=
- Computation that runs on a background thread
+
- Result published on the UI thread.
-
AsyncTask<Params, Progress, Result>
:doInBackground(args: vararg Params) : Result
onProgressUpdate(progress: Progress)
-
onPostExecute(result: Result)
-- na UI thread
- E.g.
MyAsyncTask().execute(arg1, arg2)
- Pedido HTTP com parsing JSON em background numa
AsyncTask
- E.g.
AsyncTask<String, Int, SearchDto>()
:doInBackground(vararg resp: String): SearchDto
onPostExecute(result: SearchDto) = onSuccess(result)
- !!!! Problema: Reconfiguração no meio do Pedido HTTP
=>
callback paraActivity
destroyed - Simular com
Thread.sleep(...)
na background thread durante parsing da resposta HTTP:
-
LiveData
: UI<->
Data -
LiveData
---->*
Observer
:-
Active
Observer
=>
lifecycle in{ STARTED, RESUMED }
- Só são notificados active observers
-
Observer
removido seDESTROYED
-
Active
-
MutableLiveData<T>
:-
setValue(T)
egetValue()
observe(LifecycleOwner, Observer<T>)
interface Observer<T> { void onChanged(T t);
-
- Saving UI States
- Coexixtir
ViewModel
comsaveInstanceState
e intent extras bundle - intent extras bundle pode ser usado em vez de onSaveInstanceState() bundle
-
Activity::isChangingConfigurations()
-- detectar se Activity está em reconfiguração. - Tornar
ArtistsViewModel
Parcelable
e guardar apenas o nome do artista pesquisado.
-
UI thread ou main thread:
- Despachar eventos para a UI (e.g.
onTouchEvent
) - Instanciar as activities
- NÃO pode ser bloqueada.
- Despachar eventos para a UI (e.g.
-
worker thread ou backgroung thread (BG thread):
- NÃO pode manipular a UI
-
LiveData
--setValue
-- só na UI Thread. Se chamado numa BG thread_:IllegalStateException: Cannot invoke setValue on a background thread
-
LiveData
--postValue
:- pode ser chamado numa BG thread.
- submete uma tarefa à main thread para actualização de
value
- Exemplo:
LastfmWebApi
recebe um callbackdoInSucess
em vez deonSucess
:-
AsyncTask
não redifineonPostExecute
e chamadoInSucess
na BG thread - Callback passado a
doInSucess
tem que fazerpostValue
-
- Robolectric framework -- ambiente Android para os testes unitários:
- gradle:
testImplementation 'org.robolectric:robolectric:4.3'
- gradle:
android { testOptions { unitTests { includeAndroidResources = true }}}
-
unit test:
@RunWith(RobolectricTestRunner::class)
ctr = Robolectric.buildActivity(MainActivity::class.java).setup()
-
ctr.get()
-- retorna a instância deActivity
resumed
-
unit test:
- gradle:
testImplementation 'androidx.test:core:1.0.0'
ApplicationProvider.getApplicationContext<Context>()
- ficheiro
robolectric.properties
na pastaapp/src/test/resources
com:sdk=28
- gradle:
- Implementação de um teste unitário para
ArtistsViewModel
-
testImplementation 'org.awaitility:awaitility-kotlin:4.0.1'
await.pollInSameThread().until { flushForegroundThreadScheduler(); cf.isDone() }
- DAO - Data Access Object
- Outros padrões de acesso a dados: Active Record, Data Mapper, etc (PEAA by Martin Fowler)
- SQLite DataBase
-
ROOM:
- Abstracção sobre o SQLite.
- Segue o idioma DAO.
- use case: caching parcial de dados relevantes.
- conteúdos sincronizados automaticamente via
LiveData
- ROOM -
Database
-- ponte de acesso principal à aplicação.- classe abstract anotada com
@Database
e que extende deRoomDatabase
- inclui uma lista de entidades (clases anotadas com
@Entity
) - um método abstracto sem parâmetros que retorna um DAO (clases anotadas com
@Dao
)
- classe abstract anotada com
- A instância de
Database
é obtida em runtimeRoom.databaseBuilder()
- Teste unitário
ArtistDaoTest
- ROOM dependencies:
apply plugin: 'kotlin-kapt'
...
implementation "androidx.room:room-runtime:2.2.1"
kapt "androidx.room:room-compiler:2.2.1"
- Introdução do Repositório entre
ViewModel
e o DAO - Referência ao padrão Repository
- Guide to app architecture
- Implementação de
ArtistRepository
eArtistRepositoryTest
- DAO com
@Insert(onConflict = OnConflictStrategy.REPLACE)
- Utilização de
MediatorLiveData
para actualização doViewModel
-
Room Persistence Library
-->
@Relation
- automatically fetch relation entities
-
@Embedded
- permite propriedades aninhadas (nested) -
@Relation
- aplicado a propriedades de classes (não entidade aka POJO)-
parentColumn
-- chave primária nesta classe -
entityColumn
-- chave estrangeira na entidade que refere esta classe
-
- DAO:
@Query("SELECT * FROM artists") fun getAll(): LiveData<List<ArtistAlbums>>
class ArtistAlbums (
@Embedded
var artist: Artist, // Embedded mbid primary key from Artist
@Relation(parentColumn = "mbid", entityColumn = "artistMbid")
var albums: List<Album> // Album with an artistMbid property
)
- Paging library
-
PagedList
- chunks of app's data, or pages -
PagedList
<---
ViewModel
--->
DataSource.Factory
--->
DataSource
--->
fetch Albums
- e.g.:
-
ViewModel
:val albums : LiveData<PagedList<Album>> = AlbumDataSourceFactory(artistMbid).toLiveData(50)
class AlbumDataSourceFactory(val artisMbid: String): DataSource.Factory<Int, Album>()
class AlbumDataSource(val artistMbid: String) : PageKeyedDataSource<Int, Album>()
-
- UI:
RecyclerView
--->
PagedListAdapter
--- observe -->
adapter.submitList(it)
<---- onChanged ----
LiveData<PagedList<...>>
- Dependências:
implementation "androidx.paging:paging-runtime:2.1.0"
implementation "androidx.paging:paging-runtime-ktx:2.1.0"
- Schedule tasks with WorkManager:
-
Worker
- defines the Background task -
WorkRequest
-- defines how and when should be run:OneTimeWorkRequest
orPeriodicWorkRequest
-
WorkManager
-- schedules work
-
val request = OneTimeWorkRequestBuilder<MyWorkerClass>().build()
WorkManager.getInstance(myContext).enqueue(request)
-
MyWorkerClass
extends theWorker
class:-
doWork()
-- run synchronously on a background thread -
return Result.success()
-- finishes successfully return Result.fail()
-
return Result.retry()
-- needs to be retried
-
Setup channel on Application:
-
NotificationChannel(String CHANNEL_ID, name, importance)
-- create a notification channel -
notificationManager.createNotificationChannel(channel)
-- register your app's notification channel
Build and send the notification through the previous channel:
-
PendingIntent.getActivity (context,requestCode,intent,flags)
-- set the notification's tap action -
NotificationCompat.Builder(context, CHANNEL_ID)...
-- set the notification content NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
-
Android Components:
Service
,Activity
,BroadcastReceiver
orContentProvider
-
Service
- performs long-running operations in background with NO user interface. -
Service
:- Foreground -- noticeable to the user
-
Background -- e.g.
WorkManager
runs deferrable background work - Bound -- it serves another application through IPC
- Like an
Activity
aService
runs a single thread. - When Application =
Activity
+Service
=>
they share the "main thread" - Alternative
IntentService
uses a worker thread.
- MediaPlayer overview
-
prepare()
=>
fetching and decoding media=>
DON'T do it on UI thread. -
prepareAsync()
=>
when done=>
onPrepared()
ofOnPreparedListener
- Configuration:
MediaPlayer.setOnPreparedListener(MediaPlayer.OnPreparedListener)
- States: Idle
->
Initialized->
Prepared->
Started->
Stopped | Paused | PlaybackCompleted - Releasing:
mediaPlayer?.release(); mediaPlayer = null
- If you don't release:
W/MediaPlayer-JNI: MediaPlayer finalized without being released
- Data and file storage overview
- Content provider basics
-
ContentProvider
-->
ContentResolver
-->
Cursor
-
ContentProvider
e.g.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
resolver // ContentResolver
.query(MediaStore.... /* ContentProvider */, ...)
?.use { cursor ->
while (cursor.moveToNext()) {
val title = cursor.getString(0)
val artist = cursor.getString(1)
...
-
requestPermissions()
-->
Activity::onRequestPermissionsResult()
- Request App Permissions
- Demo play tracks from external storage:
- Use
MediaPlayer
withoutService
=> stops if it looses foreground - Use
MediaPlayer
in aService
=> stops if the application is destroyed - Use
MediaPlayer
in a foregroundService
=> doesn't stop
- Use
<manifest ... ><application ... ><service android:name=".TrackService" />..
-
Component
->
startService(Intent)
->
onStartCommand(Intent)
-
Component
->
stopService(Intent)
->
onDestroy()
-
Component
->
startForegroundService
to run in Foreground:<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
-
Service must call
->
startForeground(id, Notification)
- otherwise:
Context.startForegroundService() did not then call Service.startForeground()
- otherwise: