Muscular
Labate Antonio
Rauti Samuele
Muscular - Introduzione all’app
Scopo principale:
Target:
Design - tipo di applicazione
L’applicazione per come è stata implementata è standalone perché allo stato attuale richiede solo la posizione dal GPS per garantire tutte le funzionalità. Per l’utilizzo di ipotetici utenti autonomi, l’app beneficerebbe di un’architettura client-server o cloud-based in cui i dati di esercizi e schede possano essere richiesti (che siano nuovi o un backup, magari salvati precedentemente o condivisi da altri utenti che usano l’app) all’occorrenza.
Interazione con l’esterno
Per come è stata pensata la nostra app non ha bisogno di interagire con altre applicazioni installate sul dispositivo, perché i dati di esempio sono forniti direttamente con l’apk, mentre l’utente potrebbe aggiungerne di nuovi.
Design - persistenza dei dati
Non ci è servito utilizzare dati che provengono dal resto del sistema (non è stato necessario l’uso di un content provider), ma abbiamo ampiamente usato le SharedPreferences per le feature di Localizzazione delle stringhe e di un database, che è stato implementato con l’utilizzo del framework Jetpack Room, con cui abbiamo definito un handle all’istanza di DB accessibile da tutte le activity (tramite la classe MyApplication).
Una volta inizializzato il DB possiamo usare i metodi dell’interfaccia DAO, per eseguire Insert, Delete e Query definite da noi.
Database - materializzazione della struttura
Il database è composto dalle entità e dall’interfaccia AppDao che permette di interagire con tali entità. Ognuna di esse è definita in una speciale classe con le dovute annotazioni per identificarne ogni parte:
Database - uso
Il database viene inizializzato all’avvio app (tramite la classe MyApplication) che contiene la variabile pubblica appDatabase
da qualsiasi activity quindi è possibile richiamare tale variabile per avere accesso diretto al DB come viene fatto ad esempio nella ExercisesActivity qui a fianco
Database - definizione delle query
Esempio
Definita come una normale funzione in kotlin, restituisce una lista contenenti gli oggetti ExerciseEntity da cui è possibile estrarre i valori degli attributi (esercizio.descrizione)
Le query vanno definite tutte nell’interfaccia AppDao, che abbiamo visto istanziata nella slide precedente e che mette a disposizione come metodi di kotlin le query scritte da noi.
Ovviamente non è necessario restituire un entity, infatti alcune query con aggregazione (come quella usata per ottenere il conteggio degli esercizi nelle slide precedenti) restituisce un intero.
Layout
Il layout delle activity contiene più layout innestati, generalmente LinearLayout e ConstraintLayout, la cui unione permette una grande flessibilità nel posizionamento di elementi personalizzati come esercizi, schede, tabelle, ecc… Il tutto con uno stile unificato gestito dal file style.xml
Header delle schede (container: LinearLayout) con all’interno un ConstraintLayout per adattare le dimensioni adeguate al contenuto (che cambia ad esempio durante il cambio lingua)
Layout innestati (constraint + table) per il singolo esercizio, le cui view innestate sono riempite con dei valori a runtime tramite la funzione “showExercise(container: LinearLayout)”,
analogamente facciamo per le schede di allenamento
Layout - Main Activity
Layout - Navbar
La barra di navigazione in Android è un componente dell'interfaccia utente che fornisce un modo intuitivo per spostarsi tra diverse sezioni dell'applicazione. In questo caso, abbiamo progettato una navbar con bottoni che consentono agli utenti di accedere a tre diverse attività all'interno dell'applicazione.
Nel nostro caso “Statistics” porta a una activity (non implementata) con diverse informazioni sulle abitudini di allenamento, “Workouts” alla lista delle schede di allenamento ed “Exercises” alla lista degli esercizi disponibili.
Layout - Scrollviews
Nelle activity principali, ogni componente della ScrollView è un elemento di una lista, aggiunto dopo averne recuperato info ed asset dal database.
In generale un elemento si compone dei bottoni per la rimozione/modifica, titolo, descrizione, il cui design è definito in un file .xml aggiornato dinamicamente all’inserimento.
Il tasto di rimozione oltre a rimuovere la view chiama anche la funzione di rimozione dal DB per manterere la coerenza tra UI e backend.
Layout - Scrollviews
La logica di aggiornamento è tutta contenuta nella funzione a fianco che fornito il container (in questo caso il LinearLayout all’interno della scrollview), prima esegue l’inflating del layout definito in un apposito file xml per poi aggiornare le view con le info contenute nella ProgrammeEntity, infine definisce le operazioni di rimozione e di avvio dell’allenamento (questo cambia con ExerciseActivity) e infine aggiunge la nuova view al layout
Layout - Esercizi
All’esercizio è associata anche un’immagine che vada a rappresentarlo e che, quando cliccata, va in full screen (in overlay rispetto al layout grazie a un dialog). La fluidità viene mantenuta utilizzando lo stesso asset sia per l’anteprima che per la versione full screen e grazie all’assenza del bisogno di usare un’ulteriore activity, ma solo di una view che viene mostrata (oppure no) all’occorrenza, sfruttando il suo attributo “visibility”.
Layout - Esercizi
L’unica differenza con WorkoutsActivity è la gestione dell’immagine
Nel caso di molti asset di tipo immagine (che occuperebbero molto spazio) o di un’architettura alternativa client-server per l’app (per cui potrebbero essere non disponibili), abbiamo deciso
di astrarre la logica di impostazione dell’immagine in questa funzione, che restituisce l’esito dell’assegnazione. Nel caso peggiore l’ImageView viene oscurata, come in figura:
Feature 1: Localizzazione
Prima della traduzione
Dopo che la lingua inglese è stata selezionata
Feature 1: Localizzazione
Feature 2: Notifiche
Feature 3 (Context-aware): Gestione dell’accelerometro
Feature 3: Gestione dell’accelerometro
Feature 3: Gestione dell’accelerometro -> Sensor Manager
Feature 3: Gestione dell’accelerometro
Feature 3: Gestione dell’accelerometro
Permessi dell’app
Nel Manifest sono stati specificati i permessi utilizzati dall’app, cioè quelli relativi ai sensori (accelerometro), notifiche e gps. Ovviamente non basta specificare i permessi utilizzati ma serve anche richiederli all’utente a runtime e ricordare che possono essere disattivati dalle impostazioni, quindi abbiamo implementato una verifica ed eventuale richiesta all’utente (tramite un AlertDialog)
per ogni permesso dangerous.
esempio: richiesta permesso geolocalizzazione
Feature 4 (Context-aware): Geolocalizzazione
Il servizio viene lanciato e controlla tali permessi per poi fermarsi (grazie al valore di ritorno START_NOT_STICKY, che implica che non ripartirà automaticamente), mentre se non viene violata nessuna precondizione verrà eseguita una task periodicamente e se dovesse essere interrotto ripartirebbe automaticamente (START_STICKY).
NB: si presuppone che il nostro LocationBackgroundService venga eseguito solo se l’app viene autorizzata ad accedere alla posizione (la richiesta viene fatta nella MainActivity, vedi slide precedente).
Feature 4: Geolocalizzazione - verifica posizione
Tramite il servizio Nominatim di openstreetmap eseguiamo una query che ci permette di determinare la distanza dalla palestra (il cui indirizzo è contenuto in defaultGymQuery che è un valore hardcoded, ma può essere facilmente fatta scegliere come GeoPoint da una MapView all’utente). Una volta determinato che la distanza è inferiore a quella minima di 100m viene inviata una notifica con un suggerimento sul prossimo allenamento (a non meno di 15 minuti l’una dall’altra).
Feature 4: Geolocalizzazione - determinazione suggerimento
Per inviare la notifica correttamente è necessario determinare in anticipo i valori delle schede disponibili, per poi sceglierne una in base al giorno della settimana.
L’approccio è molto semplice, usiamo l’operatore % per determinare un indice che “itera” al variare del giorno della settimana su tutte le schede disponibili, indipendentemente dal numero (purché ce ne sia almeno una), per poi inviare una notifica con il suggerimento.
OSS: sarebbe possibile anche fare partire il workout cliccando sulla notifica aggiungendo un onClick listener proprio sul banner che l’utente possa cliccare.
Grazie per l’attenzione!