Pubblicato da  Nick Butcher

Illustrazione di Virginia Poltrack

Il sistema di styling Android offre un modo efficace per specificare il design visivo della tua app, ma potrebbe essere facilmente usato in modo improprio. Il suo corretto utilizzo può semplificare la gestione di temi e stili, rendere meno "preoccupanti" gli aggiornamenti del marchio e semplificare il supporto delle modalità scure. Questo è il primo di una serie di articoli in cui ...
Pubblicato da Nick Butcher

Illustrazione di Virginia Poltrack

Il sistema di styling Android offre un modo efficace per specificare il design visivo della tua app, ma potrebbe essere facilmente usato in modo improprio. Il suo corretto utilizzo può semplificare la gestione di temi e stili, rendere meno "preoccupanti" gli aggiornamenti del marchio e semplificare il supporto delle modalità scure. Questo è il primo di una serie di articoli in cui Chris Banes e io inizieremo a smitizzare lo styling Android in modo che tu possa creare app eleganti senza strapparti i capelli.
In questo primo articolo, analizzeremo gli elementi di base del sistema di styling: temi e stili.

Tema != Stile

Sia i temi che gli stili usano la stessa sintassi <style>, ma hanno scopi molto diversi. Puoi pensare a entrambi come ad archivi di chiavi-valori in cui le chiavi sono attributi e i valori sono risorse. Analizziamoli più in dettaglio.

Cosa c'è in uno stile?

Uno stile è una raccolta di valori degli attributi di visualizzazione. Puoi pensare a uno stile come a una Map<view attribute, resource>. Le chiavi sono tutti attributi di visualizzazione, ovvero attributi dichiarati da un widget e che è possibile impostare in un file di layout. Gli stili sono specifici di un singolo tipo di widget perché widget diversi supportano diversi set di attributi:
Gli stili sono una raccolta di attributi di visualizzazione; specifici per un singolo tipo di widget

<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->
<style name="Widget.Plaid.Button.InlineAction" parent="…">
  <item name="android:gravity">center_horizontal</item>
  <item name="android:textAppearance">@style/TextAppearance.CommentAuthor</item>
  <item name="android:drawablePadding">@dimen/spacing_micro</item> </style>
Come puoi vedere, ognuna delle chiavi nello stile rappresenta elementi che potresti impostare in un layout:
<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->
<Button …
  android:gravity="center_horizontal"
  android:textAppearance="@style/TextAppearance.CommentAuthor"
  android:drawablePadding="@dimen/spacing_micro"/>

La loro estrazione in uno stile ne semplifica il riutilizzo in più visualizzazioni e la gestione.

Utilizzo

Gli stili vengono utilizzati dalle singole visualizzazioni da un layout:
<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->
<Button …
  style="@style/Widget.Plaid.Button.InlineAction"/>

Le visualizzazioni possono applicare solo un singolo stile: confronta questa proprietà con altri sistemi di styling come CSS sul Web in cui i componenti possono impostare più classi CSS.

Ambito di applicazione

Uno stile applicato a una visualizzazione si applica solo a quella visualizzazione e a nessuno dei suoi elementi secondari. Ad esempio, se hai un ViewGroup con tre pulsanti, l'impostazione dello stile InlineAction sul ViewGroup non applicherà tale stile ai pulsanti. I valori forniti dallo stile sono combinati con quelli impostati direttamente nel layout (risolti usando l'ordine di precedenza dello styling).

Che cos'è un tema?

Un tema è una raccolta di risorse denominate a cui stili, layout, ecc. possono fare riferimento in un secondo momento. Esse forniscono nomi semantici alle risorse Android in modo da potervi fare riferimento in seguito, ad esempio colorPrimary è un nome semantico per un determinato colore:
<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->
<style name="Theme.Plaid" parent="…">
  <item name="colorPrimary">@color/teal_500</item>
  <item name="colorSecondary">@color/pink_200</item>
  <item name="android:windowBackground">@color/white</item>
</style>

Queste risorse denominate sono note come attributi del tema, quindi un tema è Map<theme attribute, resource>. Gli attributi del tema sono diversi dagli attributi della visualizzazione perché non sono proprietà specifiche di un singolo tipo di visualizzazione, ma puntatori denominati semanticamente a valori applicabili in modo più ampio in un'app. Un tema fornisce valori concreti per queste risorse denominate. Nell'esempio sopra l'attributo colorPrimary specifica che il colore primario per questo tema è verde acqua. Astraendo la risorsa con un tema, possiamo fornire diversi valori concreti (come colorPrimary=orange) in temi diversi.
I temi sono una raccolta di risorse denominate, utili in generale in un'app
Un tema è simile a un'interfaccia. La programmazione su un'interfaccia consente di separare il contratto pubblico dall'implementazione consentendoti di fornire implementazioni diverse . I temi hanno un ruolo simile; scrivendo i nostri layout e stili rispetto agli attributi del tema, possiamo usarli su temi diversi, fornendo diverse risorse concrete.
Pseudo-codice approssimativamente equivalente:
/* Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 */
interface ColorPalette {
  @ColorInt val colorPrimary
  @ColorInt val colorSecondary
}

class MyView(colors: ColorPalette) {
  fab.backgroundTint = colors.colorPrimary
}

Ciò consente di variare il modo in cui viene visualizzato MyView, senza doverne creare delle varianti:
/* Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 */
val lightPalette = object : ColorPalette { … }
val darkPalette = object : ColorPalette { … }
val view = MyView(if (isDarkTheme) darkPalette else lightPalette)

Utilizzo

Puoi specificare un tema per i componenti che hanno (o sono) un Context, ad esempio Activity o Views/ViewGroups:
<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->

<!-- AndroidManifest.xml -->
<application …
  android:theme="@style/Theme.Plaid">
<activity …
  android:theme="@style/Theme.Plaid.About"/>

<!-- layout/foo.xml -->
<ConstraintLayout …
  android:theme="@style/Theme.Plaid.Foo">
  

Puoi anche impostare un tema nel codice includendo un Context esistente con un ContextThemeWrapper, che poi potresti usare per eseguire l'inflation di un layout ecc.
Il potere dei temi deriva da come li usi; puoi creare widget più flessibili facendo riferimento agli attributi del tema. Temi diversi forniscono valori concreti in un secondo momento. Ad esempio, potresti voler impostare un colore di sfondo su una sezione della gerarchia di visualizzazione:
<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->
<ViewGroup …
  android:background="?attr/colorSurface">

Invece di impostare un colore statico (#ffffff o una risorsa @color), possiamo delegare al tema usando la sintassi ?attr/themeAttributeName. Questa sintassi significa: esegui una query al tema per il valore di questo attributo semantico. Questo livello di riferimento indiretto ci consente di fornire comportamenti diversi (ad esempio, impostando un colore di sfondo diverso nei temi chiari e scuri) senza dover creare layout o stili multipli che sono per lo più identici, ad eccezione di alcune variazioni di colore. Isola gli elementi che cambiano all'interno del tema.
Usa la sintassi ?attr/themeAttributeName per eseguire una query al tema per il valore di questo attributo semantico

Ambito di applicazione

A un Theme si accede come proprietà di un Context e può essere ottenuto da qualsiasi oggetto che sia o abbia un Context, ad esempio Activity, View o ViewGroup. Questi oggetti esistono in una struttura ad albero, in cui una Activity contiene ViewGroups che contengono Views, ecc. La specifica di un tema a qualsiasi livello di questo albero viene applicata anche ai nodi discendenti, ad es. l'impostazione di un tema su un ViewGroup si applica a tutte le visualizzazioni al suo interno (contrariamente agli stili, che si applicano solo a una singola visualizzazione).
<!-- Copyright 2019 Google LLC. 
   SPDX-License-Identifier: Apache-2.0 -->
<ViewGroup …
  android:theme="@style/Theme.App.SomeTheme">
  <! - SomeTheme also applies to all child views. -->
</ViewGroup>

Ciò può essere estremamente utile, ad esempio se si desidera una sezione a tema scuro di uno schermo altrimenti chiaro. Maggiori informazioni su questo comportamento saranno fornite nel prossimo post (presto disponibile!).
Questo comportamento si applica solo al momento dell'inflation del layout. Sebbene Context offra un metodo setTheme o Theme offra un metodo applyStyle, questi devono essere chiamati prima dell'inflation. L'impostazione di un nuovo tema o l'applicazione di uno stile dopo l'inflation non aggiornerà le visualizzazioni esistenti.

Altre preoccupazioni

Comprendere le diverse responsabilità e l'interazione di stili e temi aiuta a mantenere più gestibili le risorse di styling.
Ad esempio, supponi di avere un tema blu per la tua app, ma alcuni schermi Pro restituiscono un colore viola e vuoi fornire temi scuri con colori ottimizzati. Se cercassi di raggiungere questo obiettivo utilizzando solo gli stili, dovresti creare 4 stili per le combinazioni di Pro/non-Pro e chiaro/scuro. Poiché gli stili sono specifici di un tipo di visualizzazione (Button, Switch ecc.), dovresti creare queste combinazioni per ciascun tipo di visualizzazione nell'app.


Esplosione delle combinazioni di widget/stili senza l'uso dei temi

Se invece usiamo stili e temi possiamo isolare le parti che cambiano in base al tema come attributi del tema e quindi dobbiamo solo definire un singolo stile per tipo di visualizzazione. Per l'esempio sopra potremmo definire 4 temi che forniscono ciascuno valori diversi per l'attributo del tema colorPrimary, a cui questi stili poi fanno riferimento e riflettono automaticamente il valore corretto dal tema.
Questo approccio potrebbe sembrare più complicato in quanto è necessario considerare l'interazione di stili e temi, ma ha il vantaggio di isolare le parti che cambiano in base al tema. Quindi, se la tua app cambia da blu ad arancione, è sufficiente apportare questa modifica in un unico posto. Ciò consente inoltre di evitare la proliferazione di stili. Idealmente hai solo un piccolo numero di stili per tipo di visualizzazione. Se non approfitti dei temi, è facile che il tuo file styles.xml ti sfugga di mano ed esploda con diverse varianti di stili simili, con conseguenti problemi di gestione.
Seguici nei prossimi articoli in cui esploreremo l'utilizzo di temi e stili (presto disponibili!)

Pubblicato da  Florina Muntenescu



Vocabolario Kotlin: typealias
Quando le definizioni del tipo distraggono dal significato del codice perché non sono leggibili o sono espressive o troppo lunghe, Kotlin ha la funzione giusta per te ...
Pubblicato da Florina Muntenescu



Vocabolario Kotlin: typealias
Quando le definizioni del tipo distraggono dal significato del codice perché non sono leggibili o sono espressive o troppo lunghe, Kotlin ha la funzione giusta per te: typealias! Typealias consente di fornire nomi alternativi per i tipi di classe o funzione senza introdurre un nuovo tipo.

Utilizzo di typealias

Puoi usare i typealias per nominare un tipo di funzione:
typealias TeardownLogic = () -> Unit
fun onCancel(teardown : TeardownLogic){ }
private typealias OnDoggoClick = (dog: Pet.GoodDoggo) -> Unit
val onClick: OnDoggoClick
Lo svantaggio è che il nome nasconde i parametri passati alla funzione, diminuendo la leggibilità:
typealias TeardownLogic = () -> Unit //or
typealias TeardownLogic = (exception: Exception) -> Unit
fun onCancel(teardown : TeardownLogic){
// can’t easily see what information we have 
// available in TeardownLogic
}
Typealias ti consente di abbreviare nomi generici lunghi:
typealias Doggos = List<Pet.GoodDoggo>
fun train(dogs: Doggos){ … }
Sebbene questo sia possibile, chiediti se davvero dovresti farlo. L'uso di un typealias rende davvero il tuo codice più significativo e leggibile?
Chiediti se l'uso di un typealias rende il tuo codice più comprensibile.
Se stai lavorando con un nome di classe lungo, puoi usare typealias per abbreviarlo:
typealias AVD = AnimatedVectorDrawable
Ma per questo caso d'uso, una soluzione migliore sarebbe utilizzare un import alias:
import android.graphics.drawable.AnimatedVectorDrawable as AVD
Gli import alias diventano ancora più utili quando è necessario disambiguare classi con lo stesso nome, provenienti da pacchetti diversi:
import io.plaidapp.R as appR
import io.plaidapp.about.R
I typealias sono definiti al di fuori delle classi, quindi assicurati di considerare la loro visibilità quando li usi.

Utilizzo di typealias in progetti multipiattaforma

Quando si lavora con progetti multipiattaforma, è possibile specificare interfacce nel codice comune che vengono quindi implementate nel codice della piattaforma. Per facilitare ciò, Kotlin fornisce un meccanismo di dichiarazioni attese ed effettive.
Le interfacce nel codice comune sono le dichiarazioni attese, definite utilizzando la parola chiave attesa. L'implementazione nel codice della piattaforma viene definito utilizzando la parola chiave effettiva. Se l'implementazione esiste già in una delle piattaforme e ha tutti i metodi attesi con le stesse identiche firme, è possibile utilizzare typealias per mappare il nome della classe al nome atteso.
expect annotation class Test
actual typealias Test = org.junit.Test

Qualche dettaglio in più

I typealias non introducono nuovi tipi. Ad esempio, il codice decompilato per la funzione train utilizzerà solo un elenco:
// Kotlin
typealias Doggos = List<Pet.GoodDoggo>
fun train(dogs: Doggos) { … }
// Decompiled Java code
public static final void train(@NotNull List dogs) { … }
I typealias non introducono nuovi tipi.
I typealias non introducono nuovi tipi. Per questo motivo, non devi fare affidamento su di essi per i controlli del tipo di tempo di compilazione. Invece, dovresti considerare l'utilizzo di un tipo diverso o di una classe inline. Ad esempio, supponiamo di avere la seguente funzione:
fun play(dogId: Long)
La creazione di un typealias su Long non ci aiuterà a prevenire errori quando proviamo a passare l'ID sbagliato:
typealias DogId = Long
fun pet(dogId: DogId) { … }
fun usage() {
    val cat = Cat(1L)
    pet(cat.catId) // compiles
}
I typealias sono un modo per fornire un nome più breve o più significativo a un tipo esistente. Se quello che stai cercando è sicurezza aggiuntiva, probabilmente vorrai creare un nuovo tipo.



Il corretto deployment dei sistemi di machine learning richiede che il sistema sia in grado di distinguere tra dati anomali o significativamente diversi da quelli utilizzati nell'addestramento. Ciò è particolarmente importante per i classificatori di reti neurali profonde, che potrebbero classificare tali input ...


Il corretto deployment dei sistemi di machine learning richiede che il sistema sia in grado di distinguere tra dati anomali o significativamente diversi da quelli utilizzati nell'addestramento. Ciò è particolarmente importante per i classificatori di reti neurali profonde, che potrebbero classificare tali input fuori distribuzione (out-of-distribution, OOD) in classi di distribuzione con elevata sicurezza. Ciò è di fondamentale importanza quando queste previsioni vengono utilizzate per prendere decisioni nel mondo reale.

Ad esempio, un'applicazione complessa dei modelli di machine learning per applicazioni del mondo reale è l'identificazione dei batteri basata su sequenze genomiche. Il rilevamento dei batteri è cruciale per la diagnosi e il trattamento di malattie infettive, come la sepsi, e per identificare i patogeni di origine alimentare. Ogni anno vengono scoperte nuove classi batteriche e, mentre un classificatore di reti neurali addestrato sulle classi conosciute raggiunge un'elevata precisione misurata attraverso la convalida incrociata, il deployment di un modello è complesso, poiché i dati del mondo reale sono in continua evoluzione e conterranno inevitabilmente genomi di classi non note (input OOD) e non presenti nei dati di addestramento.
Nel corso degli anni vengono continuamente scoperte nuove classi batteriche. Un classificatore addestrato sulle classi conosciute ottiene un'elevata precisione per gli input di test appartenenti a classi note, ma può classificare erroneamente input da classi sconosciute (ovvero, fuori distribuzione) in classi note con sicurezza elevata.
In "Likelihood Ratios for Out-of-Distribution Detection", presentato al NeurIPS 2019, abbiamo proposto e rilasciato un set di dati di riferimento realistico di sequenze genomiche per il rilevamento OOD ispirato alle sfide del mondo reale sopra descritte. Abbiamo testato metodi esistenti per il rilevamento OOD utilizzando modelli generativi su sequenze genomiche e abbiamo scoperto che i valori di verosimiglianza , ovvero la probabilità del modello che un input provenga dalla distribuzione stimata utilizzando i dati di distribuzione, erano spesso in errore. Questo fenomeno è stato osservato anche in recenti lavori su modelli generativi profondi di immagini. Spieghiamo questo fenomeno attraverso l'effetto delle statistiche di sfondo e proponiamo una soluzione basata sul rapporto di verosimiglianza che migliora in modo significativo la precisione del rilevamento OOD.

Perché i modelli di densità generano errori nel rilevamento OOD?
Per riprodurre il problema del mondo reale e valutare sistematicamente diversi metodi, abbiamo creato un nuovo set di dati batterici utilizzando dati provenienti dal catalogo NCBI disponibile pubblicamente delle sequenze di genomi procariotici. Per imitare i dati di sequenziamento, abbiamo frammentato i genomi in brevi sequenze di 250 coppie di basi, una lunghezza comunemente generata dall'attuale tecnologia di sequenziamento. Abbiamo quindi separato i dati in distribuzione e fuori distribuzione per data di scoperta, in modo tale che le classi batteriche scoperte prima di un tempo di cutoff fossero definite come in distribuzione e quelle scoperte successivamente come OOD.

Abbiamo quindi addestrato un modello generativo profondo sulle sequenze genomiche in distribuzione ed esaminato le prestazioni del modello nel discriminare tra input interni ed esterni alla distribuzione, tracciando i relativi valori di verosimiglianza. L'istogramma della verosimiglianza per le sequenze OOD si sovrappone in gran parte a quello delle sequenze in distribuzione, indicando che il modello generativo non è stato in grado di distinguere tra le due popolazioni per il rilevamento OOD. Risultati simili sono stati mostrati in precedenti lavori per modelli generativi profondi, ad esempio, un modello PixelCNN++ addestrato su immagini dal set di dati Fashion-MNIST (che consiste in immagini di abbigliamento e calzature) assegna una maggiore verosimiglianza alle immagini OOD dal set di dati MNIST (che è costituito da immagini di cifre 0-9).
A sinistra: Istogramma dei valori di verosimiglianza per sequenze genomiche in distribuzione e fuori distribuzione (OOD). La verosimiglianza non riesce a separare le sequenze genomiche in distribuzione e OOD. A destra: Un grafico simile per un modello addestrato su Fashion-MNIST e valutato su MNIST. Il modello assegna valori di verosimiglianza più elevati per le immagini OOD (MNIST) rispetto alle immagini in distribuzione.
Quando abbiamo studiato questa modalità di errore, abbiamo osservato che la verosimiglianza può essere influenzata dalle statistiche di sfondo. Per comprendere il fenomeno in modo più intuitivo, supponiamo che un input sia composto da due componenti, (1) un componente di sfondo caratterizzato da statistiche di sfondo e (2) un componente semantico, caratterizzato da sequenze specifiche per i dati in distribuzione. Ad esempio, un'immagine MNIST può essere modellata come sfondo più semantica. Quando l'occhio umano interpreta l'immagine, può facilmente ignorare lo sfondo e concentrarsi principalmente sulle informazioni semantiche, ad esempio il segno "/" nell'immagine qui sotto. Ma la verosimiglianza viene calcolata per tutti i pixel di un'immagine, sia quelli semantici sia quelli di sfondo. Sebbene vogliamo usare solo la verosimiglianza semantica per il processo decisionale, la verosimiglianza raw può essere dominata dallo sfondo.
In alto a sinistra: Immagini di esempio di Fashion-MNIST. In basso a sinistra: Immagini di esempio di MNIST. A destra: Componenti di sfondo e semantici in un'immagine MNIST.
Rapporti di verosimiglianza per il rilevamento OOD
Proponiamo un metodo basato sul rapporto di verosimiglianza che rimuove l'effetto dello sfondo e si concentra sulla semantica. Innanzitutto, addestriamo un modello di sfondo su input perturbati. Il metodo per perturbare l'input si ispira alle mutazioni genetiche e procede selezionando casualmente le posizioni nell'input e sostituendo il valore con un altro che abbia uguale probabilità. Per l'imaging, i valori vengono scelti casualmente tra i 256 possibili valori dei pixel e, per le sequenze di DNA, il valore viene selezionato tra i quattro possibili nucleotidi (A, T, C o G). La giusta quantità di perturbazione può "disturbare" la struttura semantica nei dati e acquisire solo lo sfondo. Quindi calcoliamo il rapporto di verosimiglianza tra il modello completo e il modello di sfondo, il componente di sfondo viene eliminato, in modo che rimanga solo la verosimiglianza semantica. Il rapporto di verosimiglianza è un punteggio contrastivo dello sfondo,ovvero cattura il significato della semantica rispetto allo sfondo.

Per valutare qualitativamente la differenza tra la verosimiglianza e il rapporto di verosimiglianza, abbiamo tracciato i relativi valori per ciascun pixel nei set di dati Fashion-MNIST e MNIST, creando heatmap che hanno le stesse dimensioni delle immagini. Questo ci consente di visualizzare quali pixel contribuiscono maggiormente ai due termini, rispettivamente. Dalle heatmap log-verosimiglianza, vediamo che i pixel di sfondo contribuiscono molto di più alla verosimiglianza rispetto ai pixel semantici. Con il senno di poi, questo non è sorprendente, dal momento che i pixel di sfondo sono costituiti principalmente da una stringa di zeri, un modello appreso molto facilmente dal modello. Un confronto tra le heatmap MNIST e Fashion-MNIST dimostra perché MNIST restituisce valori di verosimiglianza più elevati: ha semplicemente molti più pixel di sfondo! Il rapporto di verosimiglianza si concentra invece maggiormente sui pixel semantici.
A sinistra: Heatmap log-verosimiglianza per set di dati Fashion-MNIST e MNIST. A destra: Gli stessi esempi che mostrano le heatmap del rapporto di verosimiglianza. I pixel con valori più elevati sono di tonalità più chiare. La verosimiglianza è dominata dai pixel "di sfondo", mentre il rapporto di verosimiglianza si concentra sui pixel "semantici" ed è quindi migliore per il rilevamento OOD.
Il nostro metodo basato sul rapporto di verosimiglianza corregge l'effetto di sfondo e migliora significativamente il rilevamento OOD delle immagini MNIST da un punteggio AUROC di 0,089 a 0,994, in base a un modello PixelCNN++ addestrato per Fashion-MNIST. Quando viene applicato al set di dati di riferimento genomico, questo metodo ottiene prestazioni all'avanguardia su questo problema complesso, quando confrontato con altri 12 metodi di base.

Per maggiori dettagli, consulta il nostro recente articolo su NeurIPS 2019. Sebbene il nostro metodo basato sul rapporto di verosimiglianza raggiunga prestazioni all'avanguardia sul set di dati genomico, non ha ancora un'accuratezza sufficientemente elevata da raggiungere gli standard che consentano il deployment del modello in applicazioni reali. Incoraggiamo i ricercatori a contribuire con le loro soluzioni a questo importante problema e a migliorare l'attuale stato dell'arte. Il set di dati è disponibile sul nostro repository GitHub.

Ringraziamenti
Il lavoro qui descritto è stato scritto da Jie Ren, Peter J. Liu, Emily Fertig, Jasper Snoek, Ryan Poplin, Mark A. DePristo, Joshua V. Dillon, Balaji Lakshminarayanan, attraverso una collaborazione con diversi team di Google AI e DeepMind. Siamo grati per tutte le discussioni e i feedback su questo lavoro ricevuti dai revisori di NeurIPS 2019 e dai nostri colleghi di Google e DeepMind: Alexander A. Alemi, Andreea Gane, Brian Lee, D. Sculley, Eric Jang, Jacob Burnim, Katherine Lee, Matthew D. Hoffman, Noah Fiedel, Rif A. Saurous, Suman Ravuri, Thomas Colthurst, Yaniv Ovadia, insieme ai team Google Brain e TensorFlow.

Ad agosto abbiamo annunciato una nuova iniziativa (nota come Privacy Sandbox) volta a sviluppare un set di standard aperti finalizzati a incrementare in modo significativo la privacy sul Web. Il nostro obiettivo per questa iniziativa open source è rendere il Web più privato e sicuro per gli utenti, senza tuttavia smettere di supportare i publisher. Oggi, desideriamo aggiornarti sui nostri piani e chiedere il tuo aiuto per aumentare la privacy dell'esplorazione del Web.
Ad agosto abbiamo annunciato una nuova iniziativa (nota come Privacy Sandbox) volta a sviluppare un set di standard aperti finalizzati a incrementare in modo significativo la privacy sul Web. Il nostro obiettivo per questa iniziativa open source è rendere il Web più privato e sicuro per gli utenti, senza tuttavia smettere di supportare i publisher. Oggi, desideriamo aggiornarti sui nostri piani e chiedere il tuo aiuto per aumentare la privacy dell'esplorazione del Web.

Dopo il dialogo iniziale con la community del Web, siamo fiduciosi che, grazie a iterazione e feedback costanti, i meccanismi di tutela della privacy e attuazione di standard aperti come Privacy Sandbox saranno in grado di sostenere un Web supportato dalle inserzioni che sia sicuro, in modo tale da rendere i cookie di terze parti obsoleti. Una volta che questi approcci saranno stati adattati alle esigenze di utenti, publisher e inserzionisti e che avremo sviluppato gli strumenti per mitigare eventuali espedienti, elimineremo gradualmente il supporto per i cookie di terze parti in Chrome. E siamo intenzionati a raggiungere questo obiettivo entro due anni. Tuttavia non possiamo farcela da soli, ecco perché abbiamo bisogno che l'intero ecosistema Web prenda parte alla realizzazione di queste proposte. Abbiamo in programma di iniziare le prime versioni di valutazione entro la fine di quest'anno, a partire dalla misurazione delle conversioni per poi proseguire con la personalizzazione.

Gli utenti chiedono maggiore privacy, trasparenza, possibilità di scelta e controllo sul modo in cui i loro dati vengono utilizzati, ed è chiaro che l'ecosistema Web debba evolversi per poter soddisfare queste richieste crescenti. Alcuni browser hanno reagito a queste preoccupazioni bloccando i cookie di terze parti, ma noi crediamo che un simile approccio implichi conseguenze inattese che potrebbero avere un impatto negativo sia sugli utenti che sull'ecosistema Web. Riteniamo infatti che approcci drastici ai cookie, minando il modello di business di molti siti web supportati dalle inserzioni, finiscano per incoraggiare il ricorso a tecniche invasive come l'uso di impronte per sostituire i cookie, le quali rischiano di compromettere la privacy e il controllo degli utenti. La nostra convinzione è che tutti insieme, come community, possiamo e dobbiamo fare meglio.

Fortunatamente, abbiamo ricevuto feedback positivi in forum come W3C in merito al fatto che i meccanismi alla base di Privacy Sandbox rappresentino casi d'uso fondamentali e vadano nella giusta direzione. Questi feedback, e le relative proposte avanzate da altri partecipanti agli standard, rafforzano la nostra convinzione che le soluzioni nate in questo spazio possano funzionare. Inoltre, la nostra esperienza con la community degli standard nella creazione di alternative e nell'eliminazione graduale del supporto a Flash e NPAPI è la dimostrazione che insieme possiamo affrontare e superare sfide difficili.

Intanto, continueremo a lavorare per rendere le tecnologie web attuali più sicure e private. Come abbiamo già annunciato, a partire da febbraio Chrome limiterà il monitoraggio intersito non sicuro trattando i cookie che non includono un'etichetta SameSite solo come prima parte; inoltre, richiederà che i cookie etichettati per l'uso di terze parti siano accessibili tramite HTTPS. Questo renderà i cookie di terze parti più sicuri e fornirà gli utenti controlli più precisi sui cookie del browser. Parallelamente, stiamo sviluppando tecniche volte a rilevare e mitigare sistemi di tracciamento nascosto e altri espedienti grazie a nuove misure anti-rilevamento dell'impronta per scoraggiare questo genere di tecniche ingannevoli e invasive, che puntiamo a lanciare entro quest'anno.

Stiamo lavorando attivamente nell'ecosistema perché browser, publisher, sviluppatori e inserzionisti abbiano l'opportunità di sperimentare questi nuovi meccanismi, testarne l'efficacia in diverse situazioni e creare implementazioni di supporto come la selezione e la misurazione delle inserzioni, la prevenzione denial of service (DoS), misure anti-spam/frode e un'autenticazione federata.

Ci stiamo impegnando per dare vita a un Web più sicuro e sostenibile, insieme, e per riuscirci abbiamo bisogno del tuo impegno costante. Ti invitiamo a fornirci un feedback sulle proposte presentate dalla community di standard per il Web attraverso GitHub, assicurandoti che soddisfino le tue esigenze. In caso contrario, presenta un issue tramite GitHub o contatta via email il gruppo W3C. Se la tua attività si svolge sul Web, assicurati che i tuoi fornitori di tecnologia prendano parte a questo processo e condividi il tuo feedback con i gruppi commerciali che rappresentano i tuoi interessi.

Noi continueremo a tenervi aggiornati sui risultati del nostro lavoro volto ad accrescere la privacy dell'esplorazione del Web.

Pubblicato da Justin Schuh - Director, Chrome Engineering



Mentre l'industria e il mondo accademico continuano a esplorare i vantaggi dell'utilizzo del machine learning (ML) per realizzare prodotti migliori e affrontare problemi importanti, gli algoritmi e i set di dati su cui vengono addestrati possono anche riflettere o rafforzare bias ingiusti. Ad esempio, contrassegnare costantemente commenti testuali non tossici di alcuni gruppi come "spam" o "ad alta tossicità" in un sistema di moderazione porta all'esclusione di tali gruppi dalla conversazione.


Mentre l'industria e il mondo accademico continuano a esplorare i vantaggi dell'utilizzo del machine learning (ML) per realizzare prodotti migliori e affrontare problemi importanti, gli algoritmi e i set di dati su cui vengono addestrati possono anche riflettere o rafforzare bias ingiusti. Ad esempio, contrassegnare costantemente commenti testuali non tossici di alcuni gruppi come "spam" o "ad alta tossicità" in un sistema di moderazione porta all'esclusione di tali gruppi dalla conversazione.

Nel 2018, abbiamo condiviso il modo in cui Google utilizza l'AI per creare prodotti più utili, evidenziando i principi dell'AI che guideranno il nostro lavoro in futuro. Il secondo principio, "Evitare di creare o rafforzare bias ingiusti", delinea il nostro impegno a ridurre i bias ingiusti e minimizzare il loro impatto sulle persone.

Nell'ambito di questo impegno, al TensorFlow World abbiamo recentemente rilasciato una versione beta di Fairness Indicators, una suite di strumenti che consente il calcolo e la visualizzazione regolari delle metriche di equità per la classificazione binaria e multi-classe, aiutando i team a fare un primo passo verso l'identificazione di impatti non equi. I Fairness Indicators possono essere utilizzati per generare metriche per i report sulla trasparenza, come quelli utilizzati per le schede modello, per aiutare gli sviluppatori a prendere decisioni migliori su come distribuire i modelli in modo responsabile. Poiché le preoccupazioni e le valutazioni sull'equità differiscono caso per caso, in questa versione abbiamo incluso anche un case study interattivo con il set di dati Unintended Bias in Toxicity di Jigsaw per illustrare come i Fairness Indicators possano essere utilizzati per rilevare e correggere i bias in un modello di machine learning (ML) in produzione, a seconda del contesto in cui è distribuito. I Fairness Indicators sono ora disponibili in versione beta, per consentirti di provarli nei tuoi casi d'uso.

Che cos'è l'equità ML?

Il bias può manifestarsi in qualsiasi parte di una tipica pipeline di machine learning, da un set di dati non rappresentativo a rappresentazioni di modelli addestrati fino al modo in cui i risultati vengono presentati all'utente. Gli errori che derivano da questo bias possono avere un impatto sproporzionato su alcuni utenti più che su altri.

Per rilevare questo impatto diseguale, la valutazione su singole sezioni o gruppi di utenti è fondamentale, in quanto le metriche complessive possono oscurare le scarse prestazioni di determinati gruppi. Questi gruppi possono includere, a titolo esemplificativo, quelli definiti da caratteristiche sensibili come razza, etnia, genere, nazionalità, reddito, orientamento sessuale, abilità e credo religioso. Tuttavia, è anche importante tenere presente che l'equità non può essere raggiunta solo attraverso metriche e misurazioni; prestazioni elevate, anche tra le varie sezioni, non dimostrano necessariamente che un sistema sia equo. Piuttosto, la valutazione deve essere vista come uno dei primi modi, soprattutto per i modelli di classificazione, per identificare eventuali lacune nelle prestazioni.

La suite di strumenti Fairness Indicators

La suite di strumenti Fairness Indicators consente il calcolo e la visualizzazione delle metriche di equità comunemente identificate per i modelli di classificazione, come il tasso di falsi positivi e il tasso di falsi negativi, semplificando il confronto delle prestazioni tra le varie sezioni o con una sezione di riferimento. Lo strumento calcola gli intervalli di confidenza, che possono far emergere disparità statisticamente significative, ed esegue una valutazione su più soglie. Nella UI è possibile attivare/disattivare la sezione di riferimento e studiare le prestazioni di varie altre metriche. L'utente può anche aggiungere le proprie metriche per la visualizzazione, specifiche del proprio caso d'uso.

Inoltre, i Fairness Indicators sono integrati con il What-If Tool (WIT): facendo clic su una barra nel grafico dei Fairness Indicators, questi punti di dati specifici verranno caricati nel widget WIT per ulteriori ispezioni, confronti e analisi controfattuali. Ciò è particolarmente utile per set di dati di grandi dimensioni, in cui i Fairness Indicators possono essere utilizzati per identificare sezioni problematiche prima che il WIT venga utilizzato per un'analisi più approfondita.
Utilizzo dei Fairness Indicators per visualizzare le metriche per la valutazione dell'equità.
Facendo clic su una sezione nei Fairness Indicators, verranno caricati tutti i punti dati in quella sezione all'interno del widget What-If Tool. In questo caso, vengono mostrati tutti i punti dati con l'etichetta "femmina".
Il lancio della beta di Fairness Indicators include quanto segue:

Come utilizzare oggi i Fairness Indicators nei modelli

I Fairness Indicators si basano su TensorFlow Model Analysis, un componente di TensorFlow Extended (TFX) che può essere utilizzato per studiare e visualizzare le prestazioni del modello. Sulla base del flusso di lavoro ML specifico, i Fairness Indicators possono essere incorporati in un sistema in uno dei seguenti modi:
Se si utilizzano modelli e strumenti TensorFlow, come TFX:
  • Accedere ai Fairness Indicators come parte del componente Evaluator in TFX
  • Accedere ai Fairness Indicators in TensorBoard durante la valutazione di altre metriche in tempo reale
Se non si utilizzano gli strumenti TensorFlow esistenti:
  • Scaricare il pacchetto pip Fairness Indicators e utilizzare Tensorflow Model Analysis come strumento autonomo
Per i modelli non TensorFlow:
  • Utilizzare Model Agnostic TFMA per calcolare i Fairness Indicators in base all'output di qualsiasi modello

Case study Fairness Indicators

Abbiamo creato un case study e un video introduttivo che illustra come i Fairness Indicators possano essere utilizzati con una combinazione di strumenti per rilevare e mitigare i bias in un modello addestrato sul set di dati Unintended Bias in Toxicity di Jigsaw. Il set di dati è stato sviluppato da Conversation AI, un team di Jigsaw che lavora per addestrare modelli ML per proteggere le voci nelle conversazioni. I modelli sono addestrati per prevedere se i commenti testuali siano probabilmente abusivi in una varietà di dimensioni tra cui tossicità, insulto ed esplicitazione sessuale.

Il caso d'uso principale per modelli come questi è la moderazione dei contenuti. Se un modello penalizza determinati tipi di messaggi in modo sistematico (ad esempio, contrassegna spesso i commenti come tossici quando non lo sono, portando a un alto tasso di falsi positivi), tali voci verranno messe a tacere. Nel case study, abbiamo studiato il tasso di falsi positivi su sottogruppi suddivisi per parole chiave di identità di genere presenti nel set di dati, utilizzando una combinazione di strumenti (Fairness Indicators, TFDV e WIT) per rilevare, diagnosticare e adottare misure per correggere il problema sottostante.

E ora?

I Fairness Indicators sono solo il primo passo. Abbiamo in programma di espanderci verticalmente abilitando più metriche supportate, come metriche che consentono di valutare classificatori senza soglie e orizzontalmente creando librerie di riparazione che utilizzano metodi, come apprendimento attivo e min-diff. Poiché riteniamo che sia importante imparare attraverso esempi concreti, speriamo di fondare il nostro lavoro su altri casi di studio che verranno rilasciati nei prossimi mesi, man mano che saranno disponibili più funzionalità.

Per iniziare, consulta il repository GitHub di Fairness Indicators. Per ulteriori informazioni su come pensare alla valutazione dell'equità nel contesto del tuo caso d'uso, consulta questo link.

Ci piacerebbe collaborare con te per capire dove i Fairness Indicators sono più utili e dove sarebbero preziose funzionalità aggiuntive. Contattaci all'indirizzo tfx@tensorflow.org per inviarci un feedback sulla tua esperienza!

Ringraziamenti

Il team principale alla base di questo lavoro comprende Christina Greer, Manasi Joshi, Huanming Fang, Shivam Jindal, Karan Shukla, Osman Aka, Sanders Kleinfeld, Alicia Chang, Alex Hanna e Dan Nanas. Vorremmo anche ringraziare James Wexler, Mahima Pushkarna, Meg Mitchell e Ben Hutchinson per il loro contributo al progetto.

Un esempio elaborato dall'app Tivi

Questo post sul blog è il secondo dei due post che mostrano come le coroutine consentano di scrivere complesse operazioni UI asincrone in modo molto più semplice. Il primo post descrive la teoria, mentre questo mostra come è possibile risolvere un problema specifico.
Pubblicato da Chris Banes

Un esempio elaborato dall'app Tivi

Illustrazione di Virginia Poltrack

Questo post sul blog è il secondo dei due post che mostrano come le coroutine consentano di scrivere complesse operazioni UI asincrone in modo molto più semplice. Il primo post descrive la teoria, mentre questo mostra come è possibile risolvere un problema specifico.
Se vuoi consultare il primo post, puoi trovarlo qui:
Sospensione sulle viste
Prendiamo ciò che abbiamo appreso nel post precedente e applichiamolo a un caso di utilizzo di app nel mondo reale.

Il problema

Qui abbiamo la UI dei dettagli dei programmi TV dell'app di esempio Tivi. Oltre alle informazioni sui programmi, elenca le stagioni e gli episodi. Quando l'utente fa clic su uno degli episodi, i dettagli dell'episodio vengono visualizzati utilizzando un'animazione che espande l'elemento selezionato:



Espansione episodio (velocità 20%)

L'app utilizza la libreria InboxRecyclerView per gestire l'animazione di espansione precedente:
fun onEpisodeItemClicked(view: View, episode: Episode) {
    // Tell InboxRecyclerView to expand the episode item. 
    // We pass it the ID of the item to expand
    recyclerView.expandItem(episode.id)
}


InboxRecyclerView funziona fornendo l'ID elemento della vista da espandere. Quindi trova la vista corrispondente dagli elementi di RecyclerView ed esegue l'animazione su di essa.
Ora diamo un'occhiata al problema che stiamo cercando di risolvere. Nella parte superiore della stessa UI è presente un elemento diverso, che mostra all'utente il prossimo episodio da guardare. Utilizza lo stesso tipo di vista dell'elemento del singolo episodio mostrato sopra, ma ha un ID elemento diverso.
Vista la mia pigrizia, per facilitare lo sviluppo ho usato lo stesso onEpisodeItemClicked() per questo elemento. Sfortunatamente questo ha portato a un'interruzione dell'animazione al momento del clic.



Espansione elemento errata (velocità 20%)

Invece di espandere l'elemento cliccato, la libreria espande un elemento apparentemente casuale in alto. Questo non è l'effetto desiderato ed è causato da alcuni problemi sottostanti:
  1. L'ID che utilizziamo nel listener di clic viene preso direttamente dalla classe Episode. Questo ID è associato all'elemento dell'episodio singolo nell'elenco delle stagioni.
  2. L'elemento dell'episodio potrebbe non essere collegato a RecyclerView. Affinché la vista esista in RecyclerView, l'utente dovrebbe aver allargato la stagione e scorso in modo tale che l'elemento sia nell'area visibile.
A causa di questi problemi, la libreria espande il primo elemento.

Soluzione ideale

Quindi qual è il comportamento previsto? Idealmente avremmo qualcosa del genere (rallentato:



Il risultato ideale (velocità 20%)

In pseudo-codice potrebbe apparire così:
fun onNextEpisodeToWatchItemClick(view: View, nextEpisodeToWatch: Episode) {
    // Tell the ViewModel to include the season’s episodes in the
    // RecyclerView data set. This will trigger a database fetch, and update
    // the view state
    viewModel.expandSeason(nextEpisodeToWatch.seasonId)

    // Scroll the RecyclerView so that the episode is displayed
    recyclerView.scrollToItemId(nextEpisodeToWatch.id)

    // Expand the item like before
    recyclerView.expandItem(nextEpisodeToWatch.id)
}

In realtà, però, dovrebbe assomigliare di più a questo:
fun onNextEpisodeToWatchItemClick(view: View, nextEpisodeToWatch: Episode) {
    // Tell the ViewModel to include the season’s episodes in the
    // RecyclerView data set. This will trigger a database fetch
    viewModel.expandSeason(nextEpisodeToWatch.seasonId)

    // TODO wait for new state dispatch from the ViewModel
    // TODO wait for RecyclerView adapter to diff new data set
    // TODO wait for RecyclerView to layout any new items

    // Scroll the RecyclerView so that the episode is displayed
    recyclerView.scrollToItemId(nextEpisodeToWatch.id)

    // TODO wait for RecyclerView scroller to finish

    // Expand the item like before
    recyclerView.expandItem(nextEpisodeToWatch.id)
}

Come puoi vedere, bisogna aspettare un bel po' per l'esecuzione di attività asincrone! ⏳
Lo pseudo-codice qui non sembra troppo complesso, ma quando inizi a implementarlo ti ritrovi presto impantanato in un mare di callback. Ecco un tentativo di scrivere una soluzione scheletro usando callback concatenati:
fun expandEpisodeItem(itemId: Long) {
    recyclerView.expandItem(itemId)
}

fun scrollToEpisodeItem(position: Int) {
   recyclerView.smoothScrollToPosition(position)
  
   // Add a scroll listener, and wait for the RV to be become idle
   recyclerView.addOnScrollListener(object : OnScrollListener() {
        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
            if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                expandEpisodeItem(episode.id)
            }
        }
    })
}

fun waitForEpisodeItemInAdapter() {
    // We need to wait for the adapter to contain the item id
    val position = adapter.findItemIdPosition(itemId)
    if (position != RecyclerView.NO_POSITION) {
        // The item ID is in the adapter, now we can scroll to it
        scrollToEpisodeItem(itemId))
    } else {
        // Otherwise we wait for new items to be added to the adapter and try again
       adapter.registerAdapterDataObserver(object : AdapterDataObserver() {
            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
                waitForEpisodeItemInAdapter()
            }
        })
    }
}

// And tell the ViewModel to give us the expanded season data set
viewModel.expandSeason(nextEpisodeToWatch.seasonId)
// Now need to wait for the new data
waitForEpisodeItemInAdapter()

Questo codice non è particolarmente ben scritto e probabilmente non funziona, ma lo scopo è mostrare come le callback possano rendere la programmazione della UI davvero complessa. In genere, questo codice presenta alcuni problemi:

Strettamente accoppiate

Dal momento che dobbiamo scrivere la nostra transizione usando i callback, ogni "animazione" deve essere consapevole di cosa chiamare successivamente: Il callback n. 1 chiama l'animazione 2, il callback n. 2 chiama l'animazione n. 3 e così via. Queste animazioni non hanno alcuna relazione l'una con l'altra, ma siamo stati costretti ad accoppiarle insieme.

Difficile da mantenere/aggiornare

Due mesi dopo aver scritto questo codice, il tuo motion designer ti chiede di aggiungere una transizione di dissolvenza nel mezzo. Dovrai tracciare la transizione, passando attraverso ogni callback per trovare il callback corretto in cui attivare la nuova animazione. Quindi dovrai provarla...

Test

Testare le animazioni è sempre difficile, ma barcamenarsi in questo mare di callback lo rende ancora più complesso. Il test deve riguardare tutti i diversi tipi di animazione e tutti i callback per verificare che tutto funzioni correttamente. In questo articolo non analizziamo i test in dettaglio, ma è un'attività che le coroutine rendono molto più facile.

Coroutine in soccorso 

Nel primo post abbiamo imparato come includere un'API di callback in una funzione di sospensione. Usiamo questa conoscenza per trasformare il nostro terribile codice di callback in questo:
viewLifecycleOwner.lifecycleScope.launch {    
    // await until the adapter contains the episode item ID
    adapter.awaitItemIdExists(episode.id)
    // Find the position of the season item
    val seasonItemPosition = adapter.findItemIdPosition(episode.seasonId)

    // Scroll the RecyclerView so that the season item is at the
    // top of the viewport
    recyclerView.smoothScrollToPosition(seasonItemPosition)
    // ...and await that scroll to finish
    recyclerView.awaitScrollEnd()

    // Finally, expand the episode item to show the episode details
    recyclerView.expandItem(episode.id)
}

Non è decisamente più leggibile?
Le nuove funzioni di sospensione dell'attesa nascondono tutta la complessità, determinando un elenco sequenziale di chiamate di funzione. Entriamo più nei dettagli...

MotionLayout.awaitTransitionComplete()

Al momento non ci sono estensioni MotionLayout ktx disponibili e, inoltre, MotionLayout attualmente non ha la possibilità di aggiungere più di un listener alla volta (richiesta di funzionalità). Ciò significa che l'implementazione della funzione awaitTransitionComplete() è un po' più complicata rispetto ad alcune delle altre funzioni.
Usiamo una sottoclasse di MotionLayout che aggiunge il supporto per più listener: MultiListenerMotionLayout.
La nostra funzione waititTransitionComplete() viene quindi definita come:
/**
 * Wait for the transition to complete so that the given [transitionId] is fully displayed.
 * 
 * @param transitionId The transition set to await the completion of
 * @param timeout Timeout for the transition to take place. Defaults to 5 seconds.
 */
suspend fun MultiListenerMotionLayout.awaitTransitionComplete(transitionId: Int, timeout: Long = 5000L) {
    // If we're already at the specified state, return now
    if (currentState == transitionId) return

    var listener: MotionLayout.TransitionListener? = null

    try {
        withTimeout(timeout) {
            suspendCancellableCoroutine<Unit> { continuation ->
                val l = object : TransitionAdapter() {
                    override fun onTransitionCompleted(motionLayout: MotionLayout, currentId: Int) {
                        if (currentId == transitionId) {
                            removeTransitionListener(this)
                            continuation.resume(Unit)
                        }
                    }
                }
                // If the coroutine is cancelled, remove the listener
                continuation.invokeOnCancellation {
                    removeTransitionListener(l)
                }
                // And finally add the listener
                addTransitionListener(l)
                listener = l
            }
        }
    } catch (tex: TimeoutCancellationException) {
        // Transition didn't happen in time. Remove our listener and throw a cancellation
        // exception to let the coroutine know 
        listener?.let(::removeTransitionListener)
        throw CancellationException("Transition to state with id: $transitionId did not" +
                " complete in timeout.", tex)
    }
}

Adapter.awaitItemIdExists()

Questa funzione è probabilmente piuttosto di nicchia, ma è molto utile. Nell'esempio dei programmi TV precedenti, in realtà gestisce alcuni stati asincroni diversi:
// Make sure that the season is expanded, with the episode attached
viewModel.expandSeason(nextEpisodeToWatch.seasonId)
// 1. Wait for new data dispatch
// 2. Wait for RecyclerView adapter to diff new data set
// Scroll the RecyclerView so that the episode is displayed
recyclerView.scrollToItemId(nextEpisodeToWatch.id)
La funzione viene implementata usando AdapterDataObserver di RecyclerView, che viene chiamato ogni volta che cambia il set di dati dell'adattatore:
/**
 * Await an item in the data set with the given [itemId], and return its adapter position.
 */
suspend fun <VH : RecyclerView.ViewHolder> RecyclerView.Adapter<VH>.awaitItemIdExists(itemId: Long): Int {
    val currentPos = findItemIdPosition(itemId)
    // If the item is already in the data set, return the position now
    if (currentPos >= 0) return currentPos

    // Otherwise we register a data set observer and wait for the item ID to be added
    return suspendCancellableCoroutine { continuation ->
        val observer = object : RecyclerView.AdapterDataObserver() {
            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
                (positionStart until positionStart + itemCount).forEach { position ->
                    // Iterate through the new items and check if any have our itemId
                    if (getItemId(position) == itemId) {
                        // Remove this observer so we don't leak the coroutine
                        unregisterAdapterDataObserver(this)
                        // And resume the coroutine
                        continuation.resume(position)
                    }
                }
            }
        }
        // If the coroutine is cancelled, remove the observer
        continuation.invokeOnCancellation {
            unregisterAdapterDataObserver(observer)
        }
        // And finally register the observer
        registerAdapterDataObserver(observer)
    }
}

RecyclerView.awaitScrollEnd()

L'ultima funzione da evidenziare è RecyclerView.awaitScrollEnd(), che attende che lo scorrimento sia terminato:
suspend fun RecyclerView.awaitScrollEnd() {
    // If a smooth scroll has just been started, it won't actually start until the next
    // animation frame, so we'll await that first
    awaitAnimationFrame()
    // Now we can check if we're actually idle. If so, return now
    if (scrollState == RecyclerView.SCROLL_STATE_IDLE) return

    suspendCancellableCoroutine<Unit> { continuation ->
        continuation.invokeOnCancellation {
            // If the coroutine is cancelled, remove the scroll listener
            recyclerView.removeOnScrollListener(this)
            // We could also stop the scroll here if desired
        }

        addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    // Make sure we remove the listener so we don't leak the
                    // coroutine continuation
                    recyclerView.removeOnScrollListener(this)
                    // Finally, resume the coroutine
                    continuation.resume(Unit)
                }
            }
        })
    }
}

Spero che ora questo codice risulti abbastanza comprensibile. Il problema con questa funzione è la necessità di utilizzare awaitAnimationFrame() prima di eseguire il controllo fail-fast. Come menzionato nei commenti, ciò è dovuto al fatto che uno SmoothScroller inizia al successivo frame di animazione, quindi bisogna attendere che ciò accada prima di controllare lo stato di scorrimento.
awaitAnimationFrame() è un wrapper su postOnAnimation (), che ci consente di attendere il successivo step temporale dell'animazione, che in genere si verifica al rendering della visualizzazione successiva. È implementato come nell'esempio doOnNextLayout() dal primo post:
suspend fun View.awaitAnimationFrame() = suspendCancellableCoroutine<Unit> { continuation ->
    val runnable = Runnable {
        continuation.resume(Unit)
    }
    // If the coroutine is cancelled, remove the callback
    continuation.invokeOnCancellation { removeCallbacks(runnable) }
    // And finally post the runnable
    postOnAnimation(runnable)
}

Risultato finale

Alla fine, la sequenza delle operazioni è simile alla seguente:



Soluzione, suddivisa in passaggi (velocità 20%)

Rompere le catene dei callback ⛓️

Passando alle coroutine, il nostro codice è in grado di eliminare lunghissime catene di callback, che sono difficili da mantenere e testare.
La ricetta per racchiudere un'API callback/listener/observer in una funzione di sospensione è sostanzialmente la stessa per tutte le API. Spero che le funzioni mostrate in questo post siano ora di facile comprensione. Quindi non ti resta che liberare il codice della tua UI dalle catene dei callback!

Pubblicato da Carlos Mendonça (Product Manager), Coral Team Illustrazione di Coral Dev Board posta accanto alle foglie autunnali
Abbiamo già annunciato che Coral è uscito dalla beta, passando a una versione più ampia e globale. Stavolta annunciamo la nuova versione di ...
Pubblicato da Carlos Mendonça (Product Manager), Coral Team Illustrazione di Coral Dev Board posta accanto alle foglie autunnali
Abbiamo già annunciato che Coral è uscito dalla beta, passando a una versione più ampia e globale. Stavolta annunciamo la nuova versione di Mendel Linux (4.0 release Day) per Coral Dev Board e SoM, oltre a numerosi altri interessanti aggiornamenti.
Abbiamo apportato aggiornamenti significativi per migliorare le prestazioni e la stabilità. Mendel Linux 4.0 release Day si basa su Debian 10 Buster e include pipeline GStreamer aggiornate e supporto per Python 3.7, OpenCV e OpenCL. Anche il kernel Linux è stato aggiornato alla versione 4.14 e U-Boot alla versione 2017.03.3.
Abbiamo anche reso possibile l'uso della GPU di Dev Board per convertire YUV in dati di pixel RGB fino a 130 fotogrammi al secondo con una risoluzione di 1080p, ovvero da uno a due ordini di grandezza più veloce rispetto a Mendel Linux 3.0 release Chef. Queste modifiche consentono di eseguire inferenze con fonti che producono YUV come telecamere e decoder video hardware.
Per aggiornare Dev Board o SoM, segui la nostra guida per ripristinare una nuova immagine di sistema da unità USB.

MediaPipe su Coral

MediaPipe è un framework open source multipiattaforma per la creazione di pipeline di percezione del machine learning multimodali in grado di elaborare dati di streaming come video e audio. Ad esempio, puoi utilizzare MediaPipe per eseguire modelli di machine learning on-device ed elaborare video da una telecamera per rilevare, tracciare e visualizzare i punti di riferimento della mano in tempo reale.
Sviluppatori e ricercatori possono prototipare i loro casi d'uso in tempo reale sulla percezione a partire dalla creazione del grafico MediaPipe sul desktop. Quindi, possono convertire ed eseguire il deployment rapidamente del grafico su Coral Dev Board, dove il modello quantizzato TensorFlow Lite sarà accelerato dalla Edge TPU.
Come parte di questa prima release, MediaPipe mette a disposizione nuovi esempi sperimentali per il rilevamento di oggetti e volti, con il supporto per Coral Dev Board e SoM. Il codice sorgente e le istruzioni per la compilazione e l'esecuzione di ciascun esempio sono disponibili su GitHub e sul sito della documentazione di MediaPipe.

Nuovo tutorial sul progetto Teachable Sorter

Nuovo tutorial sul progetto Teachable Sorter
È disponibile un nuovo tutorial per Teachable Sorter. Teachable Sorter è una selezionatrice fisica che combina la capacità del Coral USB Accelerator di eseguire un'inferenza di latenza molto bassa con un modello ML che può essere addestrato per riconoscere e ordinare rapidamente diversi oggetti mentre cadono nel vuoto. Sfrutta la nuova Teachable Machine 2.0 di Google, un'applicazione Web che consente a chiunque di addestrare rapidamente un modello in modo divertente e pratico.
Il tutorial spiega come costruire la selezionatrice a caduta libera, che separa i marshmallow dai cereali e che può essere addestrata usando la Teachable Machine.

Coral è ora su TensorFlow Hub

All'inizio di questo mese, il team TensorFlow ha annunciato una nuova versione di TensorFlow Hub, un archivio centrale di modelli pre-addestrati. Con questo aggiornamento, l'interfaccia è stata migliorata con una pagina di destinazione e un'esperienza di ricerca nuove. I modelli Coral pre-addestrati compilati per la Edge TPU continuano a essere disponibili sul nostro sito Coral, ma alcuni sono ora disponibili anche dal TensorFlow Hub. Sul sito è possibile trovare modelli con un'interfaccia Overlay, che consente di testare le prestazioni del modello in base a un set personalizzato di immagini direttamente dal browser. Scopri l'esperienza per MobileNet v1 e MobileNet v2.
Siamo entusiasti di condividere tutto ciò che Coral ha da offrire mentre continuiamo a lavorare per far evolvere la nostra piattaforma. Per un elenco di distributori, integratori di sistemi e partner in tutto il mondo, visita la nuova pagina dedicata alle partnership di Coral. Speriamo che utilizzerai le nuove funzionalità offerte su Coral.ai come risorsa e ti incoraggiamo a continuare a inviarci feedback all'indirizzo coral-support@google.com.

Pubblicato da  Chet Haase

Illustrazione di Virginia Poltrack

Benvenuto su Now in Android, la tua guida sulle novità nel mondo dello sviluppo di Android.
Ciao a tutti, bentornati dalle vacanze! È passato un po' di tempo dall'ultimo Now in Android, perché molti di noi erano più impegnati a tavola che concentrati sui byte del software. Ma recentemente sono successe alcune cose che vale la pena analizzare mentre i nostri cervelli sfogliano collettivamente il nuovo anno e tutti proviamo a rispondere alla stessa domanda: Che cosa stavamo facendo l'ultima volta che abbiamo chiuso i nostri laptop?
Pubblicato da Chet Haase

Illustrazione di Virginia Poltrack

Benvenuto su Now in Android, la tua guida sulle novità nel mondo dello sviluppo di Android.
Ciao a tutti, bentornati dalle vacanze! È passato un po' di tempo dall'ultimo Now in Android, perché molti di noi erano più impegnati a tavola che concentrati sui byte del software. Ma recentemente sono successe alcune cose che vale la pena analizzare mentre i nostri cervelli sfogliano collettivamente il nuovo anno e tutti proviamo a rispondere alla stessa domanda: Che cosa stavamo facendo l'ultima volta che abbiamo chiuso i nostri laptop?

Nuova documentazione: Room e KTX

Una marea di documentazione

Le relazioni sono una delle cose più difficili da gestire nella vita. Non sarebbe bello se ci fosse una documentazione chiara per farle funzionare?
Il tech writer Alex Cook ha migliorato la situazione fornendo una nuova guida alle relazioni tra entità in Room. @Florina ha trattato parte di questo materiale in un recente articolo, ma ora tutte le informazioni sulle relazioni sono concentrate in un'unica posizione sul nostro sito principale dedicato alla documentazione.
Relazioni: Non possiamo risolverle, ma possiamo renderle più facili da capire... per Room.
Un'altra guida su Room fornita da Alex di recente è Prepopola il tuo database Room. Nella versione 2.2, rilasciata lo scorso ottobre, Room forniva la possibilità di pre-inizializzare il database dell'app all'avvio da un file sul dispositivo locale. Questa nuova guida ti dice come usare questa funzione.

Estensioni KTX

Android KTX fornisce funzioni di estensione di Kotlin per classi esistenti, per semplificare l'utilizzo delle API Android. Pensa a loro come a un modo per migliorare le API nel nostro mondo di compatibilità con le versioni precedenti, in cui non possiamo modificare le API principali perché altrimenti le app non funzionerebbero più. Oltre ad API più eleganti e più semplici, sfruttano le funzionalità chiave di Kotlin come lambda, parametri nominati e predefiniti, coroutine e (sì!) funzioni di estensione per migliorare ulteriormente le API e integrarsi facilmente nel flusso di sviluppo di Kotlin.
Ma il problema è: come scoprire quali funzioni di estensione esistono?
In precedenza, per trovare documenti di riferimento per una particolare funzione di estensione, dovevi sapere cosa stavi cercando per selezionare il riferimento al pacchetto appropriato che ospitava i documenti sull'estensione. È come avere le chiavi della tua auto, ma non sapere dove l'hai parcheggiata o in quale città. Una volta arrivato sul posto va tutto bene, ma trovarla può essere... difficile.



Tutti i pacchetti con estensioni KTX in un semplice sommario

Joshua Baxter del team della documentazione ha fornito una soluzione: la pagina Elenco delle estensioni di KTX. Qui troverai tutte le estensioni offerte da KTX, indicate facilmente per sezioni di contenuto sul lato destro del documento. Il documento contiene collegamenti alla dichiarazione effettiva della funzione di estensione (che può contenere ulteriori informazioni, ad esempio esempi di codice), nonché i collegamenti alla classe che viene estesa.
Ora puoi sfogliare o trovare più facilmente una determinata estensione o il set di estensioni per una determinata classe. Questo documento include anche informazioni su altre librerie KTX oltre ad AndroidX. Firebase e Play Core hanno funzioni di estensione KTX che vale la pena provare.

Librerie AndroidX

A metà dicembre sono state rilasciate alcune nuove versioni delle librerie AndroidX. Innanzitutto, c'erano alcune librerie esistenti che sono diventate stabili:
  • Biometric 1.0.1: questa libreria consente di utilizzare l'autenticazione biometrica, senza preoccuparsi delle modifiche apportate alle API della piattaforma di autenticazione sottostante nelle ultime versioni. Quest'ultima versione è incentrata per lo più sulle correzioni di bug.
  • Browser 1.2.0: la 1.2.0 introduce Dark Theme e Trusted Web Activities.
  • Enterprise 1.0.0: le API di feedback enterprise ora hanno raggiunto la stabilità con la loro prima versione.
  • Paging 2.1.1: la libreria di paging semplifica il caricamento graduale dei dati con RecyclerView. Questa versione presenta miglioramenti di funzionalità minori rispetto alla precedente.
  • Room 2.2.3: la libreria Room crea una potente astrazione e API su SQLite. La versione 2.2.3 è una versione bugfix minore.
Alcune librerie sono state inoltre rilasciate in versione alpha. In particolare, non lasciarti sfuggire una nuova libreria che è stata rilasciata nella sua prima versione alpha (quindi forse non è ancora pronta per la prima serata, ma è interessante da provare se ti interessa questa funzionalità):

Articolo: osservazione dei database di Room con Flow





Nella versione 2.2.0, Room ha aggiunto la possibilità di utilizzare l'API Flow di Kotlin per poter osservare le modifiche nel database. Florina Muntenescu ha pubblicato Room Flow, mostrando come eseguire questa azione.

Codelab: Coroutine avanzate con Kotlin Flow e LiveData

Tiem Song e Sean McQuillan hanno creato questo codelab per il workshop Kotlin in Android alla KotlinConf, per mostrare come utilizzare LiveData con Coroutine e la nuova API Flow di Kotlin. Segui il codelab per un'esperienza completa o tuffati direttamente nel codice se preferisci.

Video della conferenza

Oggi uno degli aspetti più belli delle conferenze, rispetto a qualche anno fa, è che le sessioni sono generalmente registrate e pubblicate per poter essere messe a disposizione di tutti.
Trovo ancora utile andare a una conferenza di persona per vivere quella sensazione di immergersi nei contenuti per uno o più giorni, ma anche perché posso parlare con tutti i partecipanti. Ma non è fisicamente possibile partecipare a tutte le conferenze a cui mi piacerebbe andare, né è possibile guardare tutti i contenuti live di un evento con molteplici interventi concomitanti. È fantastico avere disponibili le sessioni online subito dopo questi eventi, in modo da poter recuperare tutto ciò che ho perso (a una velocità maggiore!).
Di recente sono stati pubblicati i video di due eventi recenti:

Droidcon SF





Droidcon San Francisco si è svolto nell'ultima settimana di novembre e ha trattato numerosi contenuti Android, dalla UI all'iniezione di dipendenze, dagli strumenti a Kotlin a... molti altri argomenti.

Video Kotlinconf





La KotlinConf si è svolta ai primi di dicembre nella mite Copenaghen. Ci sono state molte sessioni su Android e su Kotlin (comprese le funzionalità relative a linguaggi e multipiattaforma).

Podcast ADB

Dall'ultimo Now su Android sono stati pubblicati un paio di episodi di Android Developers Backstage. Dai un'occhiata ai link seguenti o aprili nel tuo client podcast preferito:
ADB 129: Display, Input, and Haptics



Michael, Chet e Romain (non in un pub)

In questo episodio, Chet e Romain vanno fino a Londra per fare due chiacchiere con Michael Wright. Non è la prima partecipazione di Michael al podcast e ancora una volta la discussione riguarda display, dispositivi di input e aptiche.
ADB 130: First Law of Motion… Layout




In questo episodio, Tor, Romain e Chet chiacchierano con Nicolas Roard e John Hoford del team di Android Studio su Motion Layout - e su ConstraintLayout ed editing visivo nell'IDE.

Beh...

È tutto per ora. Vai a dare un'occhiata ai nuovi documenti su Relazioni in Room, Prepopolazione in Room e KTX! Gioca con le nuove versioni di AndroidX! Leggi informazioni sull'osservazione delle modifiche al database con Room and Flow! Dacci dentro con l'IDE e immergiti in un codelab su LiveData/coroutine! Prepara i popcorn e guarda tutti i video delle sessioni del Droidcon SF e della KotlinConf! Vai ad ascoltare il podcast ADB! E torna presto per non perderti il prossimo aggiornamento dall'universo degli sviluppatori Android.

Pubblicato da Patricia Correa, Director, Developer Marketing
Banner Indie Games Festival
La community di sviluppatori indipendenti ha pubblicato su Google Play nel 2019 molti titoli fantastici, mostrando l'abilità tecnica e il design innovativo che li rende una parte essenziale del panorama del gaming.
Pubblicato da Patricia Correa, Director, Developer Marketing
Banner Indie Games Festival
La community di sviluppatori indipendenti ha pubblicato su Google Play nel 2019 molti titoli fantastici, mostrando l'abilità tecnica e il design innovativo che li rende una parte essenziale del panorama del gaming.
Per continuare ad aiutare gli sviluppatori indipendenti, oggi annunciamo l'edizione 2020 del nostro annuale Google Play Indie Games Festival. Quest'anno ospiteremo tre concorsi per sviluppatori di diversi paesi europei*, Giappone e Corea del Sud.

Premi:

I premi sono pensati per aiutarti a far crescere la tua attività e comprendono:
  • La possibilità di mettersi in mostra agli eventi finali a Varsavia, Tokyo o Seul
  • Promozioni sul Google Play Store
  • Promozioni sui nostri canali rivolti ai consumatori e agli sviluppatori
  • Accesso a eventi Google esclusivi dedicati agli sviluppatori
  • Sessioni di consultazione personalizzate con membri del team di Google
  • E altro ancora!

Ammissibilità:

I concorsi sono aperti a sviluppatori di paesi selezionati, con non più di 50 dipendenti. Il gioco inviato deve essere nuovo, rilasciato almeno in open beta tra il 7 maggio 2019 e il 2 marzo 2020. Gli altri requisiti sono riportati nei termini e condizioni per ciascuno dei concorsi.

Procedura:

banner procedura per Indie Games Festival Compila il modulo facendo clic qui. Le iscrizioni sono aperte fino al 2 marzo 2020 alle ore 15:00 CET.
I primi 20 posti in ciascuna regione saranno comunicati a marzo e invitati a partecipare agli eventi del Festival, in cui i partecipanti all'evento, gli esperti del settore e il team di Google sceglieranno i primi 10 classificati. I primi 10 presenteranno i loro giochi sul palco e verranno selezionati i 3 vincitori.

Non hai un gioco da inviare? Vieni a partecipare comunque:

Anche se non hai inviato un gioco per il concorso, ci piacerebbe vederti in uno degli eventi del Festival il 25 aprile 2020.
Scopri di più e iscriviti all'indirizzo g.co/play/indiefestival
* Il concorso europeo è aperto agli sviluppatori dei seguenti paesi: Austria, Belgio, Bielorussia, Bulgaria, Croazia, Repubblica Ceca, Danimarca, Estonia, Finlandia, Francia, Germania, Ungheria, Israele, Italia, Lettonia, Lituania, Paesi Bassi, Norvegia, Polonia, Portogallo, Romania, Russia, Slovacchia, Slovenia, Spagna , Svezia, Turchia, Ucraina e Regno Unito (compresa l'Irlanda del Nord).
Quanto hai trovato utile questo post del blog?