Utilizzare i modelli Kotlin comuni con Android

Questo argomento si concentra su alcuni degli aspetti più utili della lingua kotlin durante lo sviluppo per Android.

Utilizzo dei frammenti

Le sezioni seguenti utilizzano esempi di Fragment per mettere in evidenza alcune delle caratteristiche di Kotlin le migliori funzionalità.

Ereditarietà

Puoi dichiarare una classe in Kotlin con la parola chiave class. Nel seguente Ad esempio, LoginFragment è una sottoclasse di Fragment. Puoi indicare ereditarietà utilizzando l'operatore : tra la sottoclasse e la relativa classe padre:

class LoginFragment : Fragment()

In questa dichiarazione del corso, LoginFragment è responsabile della chiamata al metodo costruttore della sua superclasse, Fragment.

All'interno di LoginFragment, puoi eseguire l'override di una serie di callback del ciclo di vita Rispondere ai cambiamenti di stato nel tuo Fragment. Per eseguire l'override di una funzione, utilizza override parola chiave, come mostrato nell'esempio seguente:

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

Per fare riferimento a una funzione nella classe principale, utilizza la parola chiave super, come mostrato nel seguente esempio:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}

Nullability e inizializzazione

Negli esempi precedenti, alcuni parametri dei metodi sottoposti a override hanno tipi con il suffisso ?. Ciò indica che gli argomenti passati per questi parametri possono essere nulli. Assicurati di gestire in modo sicuro i propri file nulli.

In Kotlin, devi inizializzare le proprietà di un oggetto quando dichiari l'oggetto. Ciò implica che quando ottieni un'istanza di una classe, puoi immediatamente far riferimento a qualsiasi sua proprietà accessibile. Gli oggetti View in un Fragment, ma non sono pronti per essere gonfiato fino a quando non chiami Fragment#onCreateView, quindi devi trovare un modo per rinviare l'inizializzazione delle proprietà per un View.

lateinit ti consente di rimandare l'inizializzazione delle proprietà. Quando usi lateinit, dovresti inizializzare la tua proprietà il prima possibile.

L'esempio seguente mostra l'utilizzo di lateinit per assegnare View oggetti in onViewCreated:

class LoginFragment : Fragment() {

    private lateinit var usernameEditText: EditText
    private lateinit var passwordEditText: EditText
    private lateinit var loginButton: Button
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        usernameEditText = view.findViewById(R.id.username_edit_text)
        passwordEditText = view.findViewById(R.id.password_edit_text)
        loginButton = view.findViewById(R.id.login_button)
        statusTextView = view.findViewById(R.id.status_text_view)
    }

    ...
}

Conversione da SAM

Puoi rimanere in ascolto degli eventi di clic in Android implementando il parametro interfaccia di OnClickListener. Button di oggetti contengono un setOnClickListener() che accetta un'implementazione di OnClickListener.

OnClickListener ha un singolo metodo astratto, onClick(), che devi da implementare. Perché setOnClickListener() accetta sempre un OnClickListener come un argomento e poiché OnClickListener ha sempre lo stesso singolo astratto questa implementazione può essere rappresentata utilizzando una funzione anonima Kotlin. Questo processo è noto come conversione di un metodo astratto singolo, o conversione da SAM.

La conversione da SAM può rendere il tuo codice notevolmente più pulito. Nell'esempio che segue mostra come utilizzare la conversione SAM per implementare OnClickListener per un Button:

loginButton.setOnClickListener {
    val authSuccessful: Boolean = viewModel.authenticate(
            usernameEditText.text.toString(),
            passwordEditText.text.toString()
    )
    if (authSuccessful) {
        // Navigate to next screen
    } else {
        statusTextView.text = requireContext().getString(R.string.auth_failed)
    }
}

Il codice all'interno della funzione anonima passata a setOnClickListener() viene eseguito quando un utente fa clic su loginButton.

Oggetti associati

Oggetti companion forniscono un meccanismo per definire variabili o funzioni collegate concettualmente a un tipo, ma non sono legati a un particolare oggetto. modalità Complementare sono simili all'utilizzo della parola chiave static di Java per variabili e metodi.

Nell'esempio seguente, TAG è una costante String. Non è necessario un ID univoco di String per ogni istanza di LoginFragment, quindi dovresti definiscilo in un oggetto associato:

class LoginFragment : Fragment() {

    ...

    companion object {
        private const val TAG = "LoginFragment"
    }
}

Puoi definire TAG al livello superiore del file, ma potrebbe anche avere un gran numero di variabili, funzioni e classi definiti anche al livello superiore. Gli oggetti companion aiutano a connettersi variabili, funzioni e la definizione della classe senza fare riferimento ad alcuna particolare istanza di quella classe.

Delega proprietà

Durante l'inizializzazione delle proprietà, potresti ripetere alcune delle operazioni più comuni pattern, come l'accesso a un ViewModel all'interno di un Fragment. Per evitare l'eccesso codice duplicato, puoi usare la sintassi della delega delle proprietà di Kotlin.

private val viewModel: LoginViewModel by viewModels()

La delega della proprietà fornisce un'implementazione comune che puoi riutilizzare in tutta l'app. Android KTX fornisce alcuni delegati per le proprietà. viewModels, ad esempio, recupera un ViewModel che ha come ambito Fragment corrente.

La delega delle proprietà utilizza la riflessione, che aumenta il sovraccarico del rendimento. La soluzione migliore è una sintassi concisa che fa risparmiare tempo nello sviluppo.

Nullabilità

Kotlin fornisce rigide regole con supporto dei valori null che mantengono la sicurezza dei tipi ovunque. la tua app. In Kotlin, i riferimenti agli oggetti non possono contenere valori nulli predefinito. Per assegnare un valore nullo a una variabile, devi dichiarare un valore nullable. di variabile aggiungendo ? alla fine del tipo di variabile.

Ad esempio, la seguente espressione non è valida in Kotlin. name è di tipo String e non è possibile assegnare valori null:

val name: String = null

Per consentire un valore nullo, devi utilizzare un tipo String con valore nullo, String?, come come mostrato nell'esempio seguente:

val name: String? = null

Interoperabilità

Le rigide regole di Kotlin rendono il tuo codice più sicuro e conciso. Queste regole abbassano la possibilità che l'app abbia un NullPointerException in modo anomalo. Inoltre, riducono il numero di controlli nulli che devi effettuare nei tuoi le API nel tuo codice.

Spesso, devi chiamare anche un codice diverso da Kotlin quando scrivi un'app per Android, la maggior parte delle API Android è scritta nel linguaggio di programmazione Java.

Nullability è un'area chiave in cui Java e Kotlin hanno un comportamento diverso. Il prezzo di Java è inferiore rigorosamente con sintassi con nullità.

Ad esempio, la classe Account ha alcune proprietà, tra cui una String denominata name. Java non ha le regole di Kotlin sui valori nulli, affidarsi invece alle annotazioni di nullità facoltative per dichiarare esplicitamente se puoi assegnare un valore nullo.

Poiché il framework Android è scritto principalmente in Java, potresti incontrare questo scenario quando chiami nelle API senza annotazioni con valore nullo.

Tipi di piattaforma

Se utilizzi Kotlin per fare riferimento a un membro name non annotato definito in un Java Account, il compilatore non sa se String è mappato a una String o String? in Kotlin. Questa ambiguità è rappresentata da un tipo di piattaforma, String!.

String! non ha un significato speciale per il compilatore Kotlin. String! può rappresentare String o String? e il compilatore ti consente di assegnare il valore per entrambi i tipi. Tieni presente che rischi di lanciare un NullPointerException se rappresenta il tipo come String e assegna un valore nullo.

Per risolvere questo problema, devi utilizzare annotazioni nulle ogni volta che scrivi in Java. Queste annotazioni sono utili per gli sviluppatori Java e Kotlin.

Ad esempio, ecco la classe Account definita in Java:

public class Account implements Parcelable {
    public final String name;
    public final String type;
    private final @Nullable String accessId;

    ...
}

Una delle variabili membro, accessId, è annotata con @Nullable, a indicare che può contenere un valore nullo. In questo modo Kotlin tratterebbe accessId come String?.

Per indicare che una variabile non può mai essere nulla, utilizza l'annotazione @NonNull:

public class Account implements Parcelable {
    public final @NonNull String name;
    ...
}

In questo scenario, name è considerato un String senza valori null in Kotlin.

Le annotazioni di nullità sono incluse in tutte le nuove API Android e in molte API Android. Molte librerie Java hanno aggiunto annotazioni null per migliorare supportare sviluppatori sia Kotlin che Java.

Gestione dei valori null

Se hai dubbi su un tipo Java, considera che sia possibile specificare valori null. Ad esempio, il membro name della classe Account non è annotato, quindi deve assumere che sia un String con valori null.

Se vuoi tagliare name in modo che il suo valore non includa iniziali o uno spazio vuoto finale, puoi usare la funzione trim di Kotlin. Puoi tagliare in sicurezza String? in diversi modi. Uno di questi modi è utilizzare la funzione not-null operatore asserzione, !!, come mostrato nell'esempio seguente:

val account = Account("name", "type")
val accountName = account.name!!.trim()

L'operatore !! tratta tutto ciò che si trova sul lato sinistro come non null, quindi in questo caso, stai trattando name come un String non nullo. Se il risultato a sinistra è nullo, l'app genera un NullPointerException. Si tratta di un operatore rapido e semplice, ma va usato con parsimonia perché reintroduci le istanze di NullPointerException nel codice.

Una scelta più sicura consiste nell'utilizzare l'operatore di chiamata sicura, ?., come mostrato nell' nell'esempio seguente:

val account = Account("name", "type")
val accountName = account.name?.trim()

Utilizzando l'operatore di chiamata sicura, se name è diverso da null, il risultato di name?.trim() è un valore del nome senza spazi vuoti iniziali o finali. Se name è nullo, il risultato di name?.trim() è null. Ciò significa che la tua app non può mai generare un NullPointerException quando esegui questa istruzione.

Anche se l'operatore della chiamata sicura ti evita di avere un potenziale NullPointerException, ma passa un valore nullo all'istruzione successiva. Puoi gestire un valore nullo direttamente utilizzando un operatore Elvis (?:), come mostrato esempio:

val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"

Se il risultato dell'espressione sul lato sinistro dell'operatore Elvis null, il valore sul lato destro è assegnato a accountName. Questo è utile per fornire un valore predefinito che altrimenti sarebbe nullo.

Puoi anche utilizzare l'operatore Elvis per tornare in anticipo da una funzione, come mostrato nel seguente esempio:

fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point
    account ?: return

    ...
}

Modifiche all'API Android

Le API Android stanno diventando sempre più compatibili con Kotlin. Molti dei dispositivi Android le API più comuni, tra cui AppCompatActivity e Fragment, contengono annotazioni NULL e alcune chiamate come Fragment#getContext hanno più alternative compatibili con Kotlin.

Ad esempio, se accedi all'Context di un Fragment è quasi sempre non null, poiché la maggior parte delle chiamate effettuate in Fragment avviene mentre Fragment è collegata a Activity (una sottoclasse Context). Detto questo, Fragment#getContext non restituisce sempre un valore diverso da null, in quanto sono presenti scenari in cui un Fragment non è associato a un Activity. Di conseguenza, il ritorno il tipo di Fragment#getContext è null.

Poiché il valore Context restituito da Fragment#getContext è nullo (e è annotato come @Nullable), devi considerarlo come Context? nel tuo codice Kotlin. Ciò significa applicare uno degli operatori citati in precedenza per con valore nullo prima di accedere alle sue proprietà e funzioni. Per alcuni di questi Android contiene API alternative che offrono questa convenienza. Fragment#requireContext, ad esempio, restituisce un Context non nullo e restituisce un IllegalStateException se chiamato quando un Context è null. In questo modo puoi trattare il valore Context risultante come non null senza dover operatori o soluzioni alternative per le chiamate sicure.

Inizializzazione della proprietà

Le proprietà in Kotlin non sono inizializzate per impostazione predefinita. Devono essere inizializzati quando la classe che lo contiene viene inizializzata.

Puoi inizializzare le proprietà in diversi modi. Nell'esempio che segue mostra come inizializzare una variabile index assegnandole un valore nel dichiarazione del corso:

class LoginFragment : Fragment() {
    val index: Int = 12
}

Questa inizializzazione può essere definita anche in un blocco di inizializzazione:

class LoginFragment : Fragment() {
    val index: Int

    init {
        index = 12
    }
}

Negli esempi precedenti, index viene inizializzato quando un LoginFragment viene creato.

Tuttavia, potresti avere alcune proprietà che non possono essere inizializzate durante l'oggetto edilizia. Ad esempio, potresti voler fare riferimento a un View dall'interno di una Fragment, quindi prima il layout deve essere gonfiato. L'inflazione non si verificano quando viene creato un Fragment. È invece gonfiato quando chiami Fragment#onCreateView.

Un modo per risolvere questo scenario è dichiarare la vista come null e inizializzalo il prima possibile, come mostrato nell'esempio seguente:

class LoginFragment : Fragment() {
    private var statusTextView: TextView? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
    }
}

Anche se funziona come previsto, ora devi gestire il valore null dell'View ogni volta che ci fai riferimento. Una soluzione migliore è utilizzare lateinit per View come mostrato nell'esempio seguente:

class LoginFragment : Fragment() {
    private lateinit var statusTextView: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)

            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
    }
}

La parola chiave lateinit ti consente di evitare di inizializzare una proprietà quando in cui viene creato l'oggetto. Se viene fatto riferimento alla tua proprietà prima di essere inizializzata, Kotlin lancia un UninitializedPropertyAccessException, quindi assicurati di inizializzare la proprietà il prima possibile.