1 of 27

Muscular

Labate Antonio

Rauti Samuele

2 of 27

Muscular - Introduzione all’app

  • Muscular è un’app che permette di tenere traccia del proprio benessere e di pianificare i propri allenamenti�
  • È stata progettata per permettere agli utenti di gestire il proprio workout nella maniera più semplice e intuitiva possibile, in modo da mantenere alto il focus sull’allenamento.
  • Appassionati di fitness e body builder di qualsiasi livello

  • L’interfaccia semplice e le funzionalità implementate si adattano bene sia a principianti che ad atleti più avanzati.

Scopo principale:

Target:

3 of 27

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.

4 of 27

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.

5 of 27

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:

  • @Entity che identifica l’entità al cui interno definisco:
    • la lista foreignKeys (in cui definisco le chiavi esterne)
    • la lista indices (che crea più indici per le query)
  • @PrimaryKey con opzione per autoincrementare l’identificatore (se numerico)
  • i diversi attributi e il loro tipo

6 of 27

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

7 of 27

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.

8 of 27

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

9 of 27

Layout - Main Activity

  • La schermata principale presenta in primo piano il logo dell’app e i vari pulsanti che permettono di navigare attraverso le altre activities.

  • È presente anche un bottone che permette di mostrare a schermo uno spinner con le varie lingue in cui l’app è stata tradotta, in modo che l’app sia usufruibile da utenti di diverse nazionalità.

10 of 27

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.

11 of 27

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.

12 of 27

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

13 of 27

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

14 of 27

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:

15 of 27

Feature 1: Localizzazione

  • Alla prima installazione l’app sceglie in automatico la lingua di default del dispositivo�
  • Selezionando una nuova lingua tutte le stringhe presenti nell’applicazione verranno aggiornate per rispettare la scelta effettuata, comprese quelle derivanti da database.�
  • La lingua selezionata viene:
    • caricata attraverso la funzione loadLocate()
    • impostata tramite setLocate()
    • salvata utilizzando le SharedPreferences dell’app
    • visualizzata tramite recreate()�

Prima della traduzione

Dopo che la lingua inglese è stata selezionata

16 of 27

Feature 1: Localizzazione

  • Viene sfruttata la peculiarità di android dei qualifiers o delle alternative per il salvataggio di tutte le stringhe salvate nei file xml.�
  • È presente una versione del file strings per ognuna delle lingue rese disponibili.�
  • Alcune stringhe per comodità sono state caratterizzate come non-translatable, per cui hanno un solo valore, quello nel file xml di default.�
  • Per evitare operazioni troppo complesse, le stringhe provenienti da database sono invece state tradotte a mano, inserendo un nuovo attributo per ogni lingua.

17 of 27

Feature 2: Notifiche

  • Il sistema di notifiche è basato su 3 tipologie principali di messaggi:
    • notifiche ricorrenti come quella in figura che ricordano all’utente di mantenersi idratato
    • notifiche triggerate dall’utente durante il workout attraverso il sensore dell’accelerometro
    • notifiche che aggiornano il messaggio al proprio interno ad esempio tenendo traccia dello scorrere di un timer�
  • Le notifiche sono gestite attraverso due classi principali: NotificationHelper e NotificationReceiver.�
  • La prima si occupa della creazione delle notifiche stesse, della gestione del testo, delle immagini e il resto del contenuto da mostrare. Contiene inoltre un companion object che stabilisce il canale di invio della notifica.�
  • La seconda si occupa invece delle azioni da intraprendere alla ricezione della notifica, ad esempio l’esecuzione di una parte di codice o l’aggiornamento di alcuni dati dell’app.

18 of 27

Feature 3 (Context-aware): Gestione dell’accelerometro

  • Quando l’utente ha scelto il programma del giorno e lo visualizza a schermo, è invitato a far partire l’allenamento tramite l’apposito pulsante, che cambierà colore.�
  • Ognuno, inoltre, potrà definire a suo piacimento il tempo di riposo tra le varie serie di un esercizio.
  • Abbiamo deciso di supporre che durante la messa in atto di un esercizio, il telefono venga appoggiato su una qualsiasi superficie.

19 of 27

Feature 3: Gestione dell’accelerometro

  • Quando l’esercizio è terminato l’utente riprende il telefono in mano, attivando così l’accelerometro.�
  • Sarà dunque visualizzata a schermo una notifica “dinamica”, con un timer che si aggiornerà ogni secondo in base al tempo di riposo scelto.�
  • NB: l’utilizzo dell’accelerometro è soggetto solo a dichiarazione nel manifest e non a richiesta esplicita all’utente perché rientra nella categoria “permission normal” e non “dangerous”.
  • Allo scadere del timer, anche se l’utente sarà uscito dall’applicazione, visualizzerà un toast che lo invita a riprendere il suo allenamento.

20 of 27

Feature 3: Gestione dell’accelerometro -> Sensor Manager

  • Dando uno sguardo al codice evidenziamo la presenza di un listener che intercetta gli eventi dell’accelerometro e di una funzione che stabilisce il codice da eseguire di conseguenza, nel nostro caso viene fatto partire il timer per la notifica�
  • Per evitare che più notifiche possano essere inviate quando il telefono viene spostato durante il suo utilizzo, abbiamo imposto un intervallo di 2 minuti tra due eventi da considerare validi�
  • La condizione abs(event.values[0]) ci permette di misurare spostamenti del dispositivo sull’asse x, avremmo potuto scegliere anche movimenti sugli altri assi o combinazioni dei tre, il valore 10 è arbitrario ed è stato inserito al fine di ignorare eventuali movimenti troppo lievi o accidentali.

21 of 27

Feature 3: Gestione dell’accelerometro

  • Ricevuti i giusti segnali, viene generata la notifica attraverso le apposite classi, formata da alcune stringhe riprese dai file .xml, nella lingua preferita dell’utente, e da un valore dinamico salvato nella variabile secondsRemaining che indica il tempo di riposo rimanente e cambia ad ogni secondo.�
  • Allo scadere del timer vengono ripristinati i booleani che permetteranno l’invio della prossima notifica e viene lanciato il toast che suggerisce all’utente di riprendere il suo allenamento, sempre nella lingua opportuna.

22 of 27

Feature 3: Gestione dell’accelerometro

  • È importante che le risorse utilizzate, come i sensori, vengano rilasciate al termine del loro utilizzo, ad esempio nei metodi onStop() e onDestroy() dell’activity, come di fianco:

23 of 27

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

24 of 27

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

25 of 27

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

26 of 27

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.

27 of 27

Grazie per l’attenzione!