Skip to content

Sumarios

Miguel Gamboa edited this page Dec 18, 2019 · 34 revisions

Aulas:


09-09-2019

  • Apresentação.
  • Âmbito da disciplina.
  • Avaliação teórica e prática (3 entregas).

  • 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 not null. Otherwise returns null.

  • Basic parts: Manifest, Main Activity e Gradle build;
  • Activity: UI Component (subclasse de Context)
  • 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
  • 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) e getStringExtra(key)
  • 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) e Paint
    • 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) e onRestoreInstanceState(Bundle)
  • Bundle:
    • Pares chave--valor
    • put<Primitive> ou put<Primitive>Array
    • Instâncias de tipos complexos => Serializable !!! Atenção aos custos !!!
      • Alternativa Parcelable
  • Implementar Serializable em Line e XyPair
  • View -- onSaveInstanceState(): Parcelable e onRestoreInstanceState(state: Parcelable)
  • Implementar Parcelable em Line e XyPair:
    • 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 em Line. Substituir Serializable por Parcelable em on<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 para SketcherViewModel
  • Transformação do desenho da aplicação Sketcher para ViewModel:
    • 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()
  • 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 ao holder
    • override fun onCreateViewHolder(parent ... ):
      1. Obtém a View correspondente a um layout
      2. Inflate parent com a View do ponto 1.
      3. Instancia e retorna um novo ViewHolder

  • Implementação da App GeniuZ para apresentação de informações da Last.fm Web API.
  • Utilização de RecyclerView com implementação de ArtistAdapter para dados só em memória (i.e. Array<ArtistDto>)
  • LayoutManager e LinearLayoutManager

  • Android HTTP framework envolve várias acções:
    1. Construção de um pedido HTTP
    2. Execução
    3. Obtenção da resposta
    4. Parsing headers e body e.g. JSON => Objecto
    5. 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:
  • 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

  • '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 da Activity para o ViewModel
  • Activity 1 -----> 1 ViewModel
  • Activity --> ViewModel --> LastfmWebApi
  • => ArtistsViewModel passa a ser instanciado por um ViewModelProvider.Factory
    • e.g. LasftfmViewModelProviderFactory
  • Application -- Classe base para manter o estado global da aplicação.
    • e.g GeniuzApp -- para manter uma instância de LastfmWebApi
    • Manifesto: <application android:name=".GeniuzApp" ... />`
  • 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 para Activity 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 se DESTROYED
  • MutableLiveData<T>:
    • setValue(T) e getValue()
    • observe(LifecycleOwner, Observer<T>)
    • interface Observer<T> { void onChanged(T t);
  • Saving UI States
  • Coexixtir ViewModel com saveInstanceState 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.
  • 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 callback doInSucess em vez de onSucess:
    • AsyncTask não redifine onPostExecute e chama doInSucess na BG thread
    • Callback passado a doInSucess tem que fazer postValue
  • 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 de Activity resumed
    • gradle: testImplementation 'androidx.test:core:1.0.0'
      • ApplicationProvider.getApplicationContext<Context>()
    • ficheiro robolectric.properties na pasta app/src/test/resources com: sdk=28
  • 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 de RoomDatabase
    • inclui uma lista de entidades (clases anotadas com @Entity)
    • um método abstracto sem parâmetros que retorna um DAO (clases anotadas com @Dao)
  • A instância de Database é obtida em runtime Room.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 e ArtistRepositoryTest
  • DAO com @Insert(onConflict = OnConflictStrategy.REPLACE)
  • Utilização de MediatorLiveData para actualização do ViewModel
  • 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.:
    1. ViewModel: val albums : LiveData<PagedList<Album>> = AlbumDataSourceFactory(artistMbid).toLiveData(50)
    2. class AlbumDataSourceFactory(val artisMbid: String): DataSource.Factory<Int, Album>()
    3. 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:
    1. Worker - defines the Background task
    2. WorkRequest -- defines how and when should be run: OneTimeWorkRequest or PeriodicWorkRequest
    3. WorkManager -- schedules work
val request = OneTimeWorkRequestBuilder<MyWorkerClass>().build()
WorkManager.getInstance(myContext).enqueue(request)
  • MyWorkerClass extends the Worker 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:

  1. NotificationChannel(String CHANNEL_ID, name, importance) -- create a notification channel
  2. notificationManager.createNotificationChannel(channel) -- register your app's notification channel

Build and send the notification through the previous channel:

  1. PendingIntent.getActivity (context,requestCode,intent,flags) -- set the notification's tap action
  2. NotificationCompat.Builder(context, CHANNEL_ID)... -- set the notification content
  3. NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
  • Android Components: Service, Activity, BroadcastReceiver or ContentProvider
  • 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 a Service 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() of OnPreparedListener
  • 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

resolver  // ContentResolver
  .query(MediaStore.... /* ContentProvider */, ...)
  ?.use { cursor ->
                while (cursor.moveToNext()) {
                    val title = cursor.getString(0)
                    val artist = cursor.getString(1)
...
  • Demo play tracks from external storage:
    1. Use MediaPlayer without Service => stops if it looses foreground
    2. Use MediaPlayer in a Service => stops if the application is destroyed
    3. Use MediaPlayer in a foreground Service => doesn't stop
  • <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()