Kotlin Reactive Programming för en Android-inloggningsskärm

RxJava 2.0 är ett populärt reaktivt programmeringsbibliotek som har hjälpt otaliga Android-utvecklare att skapa mycket lyhörda appar, med mindre kod och mindre komplexitet, speciellt när det gäller att hantera flera trådar.

Om du är en av de många utvecklarna som gjort omkopplaren till Kotlin, betyder det inte att du behöver ge upp på RxJava! 

I den första delen av denna serie visade jag dig hur man flyttar från programmering med RxJava 2.0 i Java till programmering med RxJava i Kotlin. Vi tittade också på hur man förlorar pannan från dina projekt genom att utnyttja RxKotlins utvidgningsfunktioner och hemligheten att dodging SAM-konverteringsproblemet som många utvecklare stöter på när de först börjar använda RxJava 2.0 med Kotlin. 

I den andra delen kommer vi att koncentrera oss på hur RxJava kan hjälpa till att lösa de problem som du stöter på i verkliga Android-projekt genom att skapa en reaktiv Android-applikation med RxJava 2.0, RxAndroid och RxBinding.

Hur kan jag använda RxJava i Real-World-projekt?

I vår Reactive Programming med RxJava och RxKotlin artikel skapade vi några enkla observabler och observatörer som skriv ut data till Android Studio logcat-men det här är inte hur du ska använda RxJava i den verkliga världen.

I den här artikeln ska jag visa dig hur du använder RxJava för att skapa en skärm som används i otaliga Android-applikationer: den klassiska Bli Medlem skärm. 

Om din app har några typ av registreringserfarenhet, då har det vanligtvis strikta regler om vilken typ av information den accepterar. Till exempel kanske lösenordet måste överstiga ett visst antal tecken, eller e-postadressen måste vara i ett giltigt e-postformat.

Medan du skulle kunna Kontrollera användarens inmatning när de slår på Bli Medlem knappen, det här är inte den bästa användarupplevelsen, eftersom det lämnar dem öppna för att skicka information som tydligt aldrig kommer att accepteras av din ansökan.

Det är mycket bättre att övervaka användaren när de skriver och ge dem en heads-up så fort det blir klart att de skriver in information som inte uppfyller dina appers krav. Genom att ge denna typ av live och pågående feedback ger du användaren möjlighet att rätta till sina misstag innan slår på det Bli Medlem knapp.

Medan du skulle kunna övervaka användaraktivitet med vanilla Kotlin, kan vi leverera denna funktionalitet med mycket mindre kod genom att anteckna hjälp av RxJava, plus några andra relaterade bibliotek.

Skapa användargränssnittet

Låt oss börja med att bygga vårt användargränssnitt. Jag ska lägga till följande:

  • Två EditTexts, där användaren kan ange sin e-postadress (Skriv in e-mail) och lösenord (Skriv in lösenord).
  • Två TextInputLayout wrappers, som omger vår Skriv in e-mail och Skriv in lösenord EditTexts. Dessa wrappers visar varning när användaren anger en e-postadress eller ett lösenord som inte uppfyller våra appers krav.
  • En knapp för lösenordsvisning, som tillåter användaren att växla mellan att maskera lösenordet och visa det som vanlig text.
  • en Bli Medlem knapp. För att behålla detta exempel fokuserat på RxJava, kommer jag inte att genomföra denna del av registreringsupplevelsen, så jag markerar den här knappen som inaktiverad.

Här är min färdiga layout:

        

Du kan kopiera / klistra in det här i din app om du vill, eller du kan bara hämta projektkällkoden från vår GitHub repo.

Skapa en reaktiv inloggningsupplevelse med Kotlin

Låt oss nu titta på hur vi kan använda RxJava, plus några relaterade bibliotek, för att övervaka användarinmatning och ge feedback i realtid. 

Jag ska ta itu med Bli Medlem skärm i två delar. I det första avsnittet visar jag dig hur du använder RxBinding-biblioteket för att registrera och svara på händelser för textbyten. I det andra avsnittet skapar vi några omvandlingsfunktioner som validerar användarens inmatning och visar då ett felmeddelande där det är lämpligt.

Skapa ett nytt projekt med de inställningar du vill ha, men när du uppmanas, se till att du väljer Inkludera Kotlin Support checkbox. 

Reagera på textändringshändelser

I det här avsnittet implementerar vi följande funktioner: 

  • Upptäck när användaren skriver i Skriv in e-mail fält.
  • Ignorera alla textändringshändelser som inträffar inom kort tid, eftersom det indikerar att användaren fortfarande skriver. 
  • Utför en åtgärd när användaren slutar skriva. I vår färdiga app är det här vi ska validera användarens inmatning, men i det här avsnittet kommer jag bara att visa en Rostat bröd

1. RxBinding 

RxBinding är ett bibliotek som gör det enklare att konvertera ett brett spektrum av användargränssnitt händelser till observables, då kan du behandla dem precis som alla andra RxJava dataström.

Vi kommer att övervaka textbyteshändelser, genom att kombinera RxBinding s widget.RxTextView med afterTextChangeEvents metod, till exempel:

RxTextView.afterTextChangeEvents (enterEmail)

Problemet med att behandla textändringshändelser som dataströmmar är det som inledningsvis både Skriv in e-mail och enterPassword EditTexts kommer att vara tomt och vi vill inte att vår app ska reagera på den här tomma staten som om den är den första datautsläppen i strömmen. RxBinding löser detta problem genom att tillhandahålla en skipInitialValue () metod, som vi ska använda för att instruera varje Observer att ignorera deras ströms ursprungliga värde.

 RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue ()

Jag tittar på RxBinding-biblioteket i större detalj i min RxJava 2 for Android Apps-artikel.

2. RxJava s .debounce () Operatör

För att kunna leverera den bästa användarupplevelsen måste vi visa relevanta lösenord eller e-postvarningar efter att användaren har skrivit, men innan de slår på Bli Medlem knapp.

Utan RxJava skulle identifieringen av detta smala tidsfönster normalt kräva att vi genomför en Timer, men i RxJava behöver vi bara tillämpa debounce () operatör till vår dataström.

Jag ska använda debounce () operatör för att filtrera bort alla textändringshändelser som händer i snabb följd, dvs när användaren fortfarande skriver. Här ignorerar vi alla textändringshändelser som händer inom samma 400-millisekundsfönster:

 RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue () .debounce (400, TimeUnit.MILLISECONDS)

3. RxAndroid s AndroidSchedulers.mainThread ()

RxAndroid bibliotekets AndroidSchedulers.mainThread ger oss ett enkelt sätt att byta till Android: s viktigaste huvudbruksanvisning.

Eftersom det bara går att uppdatera Android-användargränssnittet från huvudgränssnittet, måste vi se till att vi är i den här tråden innan vi försöker visa varningar om e-post eller lösenord och innan vi visar vår Rostat bröd

 RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue () .debounce (400, TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ())

4. Prenumerera

För att ta emot de data som emitteras av Skriv in e-mail, vi måste prenumerera på det:

 RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue () .debounce (400, TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) .subscribe 

5. Visa toast

Till sist vill vi att vår ansökan ska reagera på händelser med textändringar genom att validera användarens inmatning, men för att hålla sakerna enkla, så kommer jag bara att visa en Rostat bröd

Din kod ska se ut så här:

importera android.support.v7.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import com.jakewharton.rxbinding2.widget.RxTextView importera kotlinx.android.synthetic.main.activity_main. * Importera io.reactivex.android .schedulers.AndroidSchedulers importera java.util.concurrent.TimeUnit class MainActivity: AppCompatActivity () överträffa roliga onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_main) RxTextView.afterTextChangeEvents (enterEmail). SkipInitialValue () .debounce (400, TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) .subscribe Toast.makeText (detta, "400 millisekunder sedan senaste textändringen", Toast.LENGTH_SHORT) .show ()

6. Uppdatera dina beroende

Eftersom vi använder några olika bibliotek måste vi öppna vårt projekt build.gradle fil och lägg till RxJava, RxBinding och RxAndroid som projektberoende: 

beroenden implementation fileTree (dir: 'libs', inkluderar: ['* .jar']) implementation "org.jetbrains.kotlin: kotlin-stdlib-jdk7: $ kotlin_version" implementation "com.android.support:design:28.0. 0-alfa1 'implementation' com.android.support:appcompat-v7:28.0.0-alpha1 'implementation' com.android.support.constraint: begränsningslayout: 1.1.0 '// Lägg till RxJava beroendet // implementering' io.reactivex.rxjava2: rxjava: 2.1.9 '// Lägg till RxAndroid beroendet // implementering' io.reactivex.rxjava2: rxandroid: 2.0.2 '// Lägg till RxBinding beroende // implementation' com.jakewharton.rxbinding2: rxbindning: 2.1.1 '

Du kan testa den här delen av ditt projekt genom att installera det på din fysiska Android-smarttelefon eller -tablet eller Android Virtual Device (AVD). Välj Skriv in e-mail Redigera text och börja skriva en Rostat bröd ska visas när du slutar skriva. 

Validera användarens ingång med transformationsfunktioner

Därefter måste vi lägga ner några grundläggande regler om vilken typ av ingång vår ansökan accepterar och kontrollera sedan användarens inmatning mot detta kriterium och visa ett felmeddelande där det är lämpligt. 

Att kontrollera användarens e-post eller lösenord är en process med flera steg, så att vi lättare kan läsa vår kod, kommer jag att kombinera alla dessa steg till sig själv transformationsfunktion. 

Här är början på validera email transformationsfunktion:

// Definiera en ObservableTransformer. Ingång och utgång måste vara en sträng // privat val validateEmailAddress = ObservableTransformer observerbar -> // Använd flatmapp för att applicera en funktion till varje objekt som emitteras av Observable // observable.flatMap // Trimma vilken blankutrymme som helst i början och slutet av användarens inmatning // Observable.just (it) it.trim () // Kontrollera om inmatningen matchar Androids e-postmönster // .filter Patterns.EMAIL_ADDRESS.matcher (it) .matches ()

I ovanstående kod använder vi filtrera() operatör för att filtrera Observable-utgången baserat på huruvida den matchar Android Patterns.EMAIL_ADDRESS mönster.

I nästa del av transformationsfunktionen måste vi ange vad som händer om ingången inte matchar E-POSTADRESS mönster. Som standard utlöser varje oåterkalleligt fel ett samtal till onerror (), som avslutar dataströmmen. Istället för att sluta strömmen vill vi att vår applikation ska visa ett felmeddelande, så jag ska använda onErrorResumeNext, som instruerar Observable att svara på ett fel genom att överföra kontroll till en ny Observable, istället för att åberopa onerror (). Detta gör det möjligt för oss att visa vårt anpassade felmeddelande.

// Om användarens inmatning inte matchar e-postmönstret, kasta ett fel // .singleOrError () .onErrorResumeNext om (det är NoSuchElementException) Single.error (Undantag ("Ange en giltig e-postadress"))  annat Single.error (it) .toObservable ()

Det sista steget är att tillämpa denna transformationsfunktion i e-postdataströmmen med hjälp av .komponera() operatör. Vid denna punkt, din MainActivity.kt borde se något så här: 

importera android.support.v7.app.AppCompatActivity import android.os.Bundle import android.util.Patterns importera io.reactivex.Observable import io.reactivex.ObservableTransformer import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers importera kotlinx.android.synthetic.main.activity_main. * Importera java.util.concurrent.TimeUnit importera com.jakewharton.rxbinding2.widget.RxTextView-klassen MainActivity: AppCompatActivity () åsidosätta roligt onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_main) RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue () .map emailError.error = null it.view () .text.toString () .debounce (400, // Se till att Vi är i Android: s huvudsakliga användargränssnitt // TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) .compose (validateEmailAddress) .compose (retryWhenError passwordError.error = it.message) .subscribe () // Om appen stöter på ett fel, försök sedan igen // privat inline fu n retryWhenError (crossinline onError: (ex: Throwable) -> Enhet): ObservableTransformer = ObservableTransformer observerbar -> observerbar.retryNär fel -> // Använd plattformen () operatören för att platta alla utsläpp i en enda Observable // errors.flatMap onError (it) Observable.just ("") / / Definiera en ObservableTransformer, där vi ska utföra e-postvalideringen // privat val validateEmailAddress = ObservableTransformer observerbar -> observable.flatMap Observable.just (it) .map it.trim () // Kontrollera om användarinmatningen matchar Androids e-postmönster // .filter Patterns.EMAIL_ADDRESS.matcher (it) .matches ( ) // Om användarens inmatning inte matchar e-postmönstret, kasta ett fel // .singleOrError () .onErrorResumeNext om (det är NoSuchElementException) Single.error (Undantag ("Vänligen ange en giltig e-postadress" )) annat Single.error (it) .toObservable ()

Installera det här projektet på din Android-enhet eller AVD, och du kommer att upptäcka att e-postdelen av Bli Medlem skärmen kontrollerar nu din inmatning framgångsrikt. Prova att ange något annat än en e-postadress, och appen kommer att varna dig om att detta inte är en giltig inmatning. 

Skölj och Repeat: Kontrollera användarens lösenord

Vid denna tidpunkt har vi en fungerande funktion Skriv in e-mail fält och implementering Skriv in lösenord är oftast bara ett fall att upprepa samma steg.

Faktum är att den enda stora skillnaden är att vår validatePassword transformationsfunktionen måste kontrollera olika kriterier. Jag ska ange att användarens lösenordsinmatning måste vara minst 7 tecken långt: 

 .filtrera it.length> 7

Efter att ha repeterat alla tidigare steg, den färdiga MainActivity.kt borde se något så här: 

importera android.support.v7.app.AppCompatActivity import android.os.Bundle import android.util.Patterns importera io.reactivex.Observable import io.reactivex.ObservableTransformer import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers importera kotlinx.android.synthetic.main.activity_main. * Importera java.util.concurrent.TimeUnit importera com.jakewharton.rxbinding2.widget.RxTextView-klassen MainActivity: AppCompatActivity () åsidosätta roligt onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_main) // Svara på textändringshändelser i enterEmail // RxTextView.afterTextChangeEvents (enterEmail) // Hoppa överEmailens ursprungliga, tomma tillstånd // .skipInitialValue () // Omforma data som emitteras / / .map emailError.error = null // Konvertera användarinmatningen till en sträng // it.view (). text.toString () // Ignorera alla utsläpp som sker inom en tidsintervall på 400 millisekunder // .debounce (400 , // Se till att vi är i Android: s huvudbruksanvisning // Tim eUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) // Tillämpa valideraEmailAddress transformationsfunktionen // .compose (validateEmailAddress) // Använd funktionen retryWhenError transformation // .compose (retryWhenError emailError.error = it.message) .subscribe () // Skölj och upprepa för enterPassword EditText // RxTextView.afterTextChangeEvents (enterPassword). skipInitialValue () .map passwordError.error = null it.view () .text.toString () .debounce (400, TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) .compose (validatePassword) .compose (retryWhenError passwordError.error = it.message) .subscribe () // Om appen stöter på ett fel, försök igen / / privat inline fun retryWhenError (crossinline onError: (ex: Throwable) -> Enhet): ObservableTransformer = ObservableTransformer observerbar -> observerbar.retryNär fel -> /// Använd plattformen () operatören för att plana alla utsläpp i en enda Observable // errors.flatMap onError (it) Observable.just ("") // Definiera vår ObservableTransformer och ange att ingång och utgång måste vara en sträng // privat val validatePassword = ObservableTransformer observerbar -> observable.flatMap Observable.just (it). folder it.trim () // Bara tillåta lösenord som är minst 7 tecken långt // .filter it.length> 7 // Om lösenordet är mindre än 7 tecken och sedan kasta ett fel // .singleOrError () // Om ett fel inträffar ... // .onErrorResumeNext om (det är NoSuchElementException) // Visa följande meddelande i lösenordet Erasa TextInputLayout // Single. felet (Undantag ("Ditt lösenord måste vara 7 tecken eller mer")) Other Single.error (it) .toObservable () // Definiera en ObservableTransformer, där vi ska utföra e- val validateEmailAddress = ObservableTransformer observerbar -> observable.flatMap Observable.just (it) .map it.trim () // Kontrollera om användarinmatningen matchar Androids e-postmönster // .filter Patterns.EMAIL_ADDRESS.matcher (it) .matches ( ) // Om användarens inmatning inte matchar e-postmönstret ... // .singleOrError () .onErrorResumeNext om (det är NoSuchElementException) //// Visa följande meddelande i mailError TextInputLayout // Single.error ( Undantag ("Ange en giltig e-postadress")) Annat Single.error (it) .toObservable ()

Installera det här projektet på din Android-enhet eller AVD, och experimentera med att skriva in i Skriv in e-mail och Skriv in lösenord fält. Om du anger ett värde som inte uppfyller appens krav, visas det motsvarande varningsmeddelandet, utan du måste knacka på Bli Medlem knapp.

Du kan ladda ner det här fullständiga projektet från GitHub.

Slutsats

I den här artikeln tittade vi på hur RxJava kan hjälpa till att lösa de verkliga problemen du kommer att stöta på när du utvecklar dina egna Android-applikationer, genom att använda RxJava 2.0, RxBinding och RxAndroid för att skapa en Bli Medlem skärm. 

För mer bakgrundsinformation om RxJava-biblioteket, se till att du kolla in vår Komma igång med RxJava 2.0-artikeln.