RxJava 2.0 is een populaire reactieve programmeerbibliotheek die talloze Android-ontwikkelaars heeft geholpen om zeer responsieve apps te maken, met minder code en minder complexiteit, vooral als het gaat om het beheer van meerdere threads.
Als je een van de vele ontwikkelaars bent die de overstap naar Kotlin hebben gemaakt, betekent dit niet dat je RxJava moet opgeven!
In het eerste deel van deze serie heb ik je laten zien hoe je van het programmeren met RxJava 2.0 in Java naar programmeren met RxJava in Kotlin. We hebben ook gekeken naar hoe je boilerplate van je projecten kunt verbannen door gebruik te maken van de uitbreidingsfuncties van RxKotlin en het geheim van het ontwijken van het SAM-conversieprobleem dat veel ontwikkelaars tegenkomen wanneer ze voor het eerst RxJava 2.0 gaan gebruiken met Kotlin.
In dit tweede deel concentreren we ons op hoe RxJava kan helpen bij het oplossen van de problemen die je tegenkomt in real-life Android-projecten, door een reactieve Android-applicatie te maken met RxJava 2.0, RxAndroid en RxBinding.
In ons Reactive Programming met RxJava en RxKotlin-artikel hebben we wat eenvoudig gemaakt observabelen
en waarnemers
die gegevens afdrukken naar Android Studio's logcat-maar dit is niet hoe je RxJava in de echte wereld zult gebruiken.
In dit artikel laat ik je zien hoe je RxJava kunt gebruiken om een scherm te maken dat wordt gebruikt in talloze Android-applicaties: de klassieker Inschrijven scherm.
Als je app heeft ieder een soort aanmeldingservaring, dan heeft het doorgaans strikte regels over het soort informatie dat het accepteert. Misschien moet het wachtwoord bijvoorbeeld een bepaald aantal tekens overschrijden of moet het e-mailadres een geldig e-mailformaat hebben.
Terwijl jij kon controleer de invoer van de gebruiker zodra ze op de Inschrijven knop, dit is niet de beste gebruikerservaring, omdat het hen open laat voor het verzenden van informatie die duidelijk nooit door uw toepassing zal worden geaccepteerd.
Het is veel beter om de gebruiker tijdens het typen te controleren en ze vervolgens een waarschuwing te geven zodra duidelijk wordt dat ze informatie invoeren die niet voldoet aan de vereisten van uw app. Door dit soort live en continue feedback te bieden, geeft u de gebruiker de gelegenheid om hun fouten te corrigeren voor dat raken Inschrijven knop.
Terwijl jij kon monitor gebruikersactiviteit met behulp van vanille Kotlin, we kunnen deze functionaliteit leveren met veel minder code door de hulp in te roepen van RxJava, plus een paar andere gerelateerde bibliotheken.
Laten we beginnen met het bouwen van onze gebruikersinterface. Ik ga het volgende toevoegen:
EditTexts
, waar de gebruiker zijn e-mailadres kan invoeren (voer email in
) en wachtwoord (voer wachtwoord in
).TextInputLayout
wikkels, die ons omringen voer email in
en voer wachtwoord in
EditTexts
. Deze wrappers geven een waarschuwing weer wanneer de gebruiker een e-mailadres of wachtwoord invoert dat niet voldoet aan de vereisten van onze app.Dit is mijn voltooide lay-out:
Je kunt dit naar jouw app kopiëren / plakken als je wilt, of je kunt de projectbroncode gewoon downloaden van onze GitHub-repo.
Laten we nu kijken naar hoe we RxJava en enkele gerelateerde bibliotheken kunnen gebruiken om gebruikersinvoer te monitoren en in realtime feedback te geven.
Ik zal de Inschrijven scherm in twee delen. In het eerste gedeelte laat ik zien hoe je de RxBinding-bibliotheek gebruikt om te registreren en te reageren op tekstwijzigingsgebeurtenissen. In het tweede gedeelte maken we enkele transformatiefuncties die de invoer van de gebruiker valideren en vervolgens, indien nodig, een foutmelding weergeven.
Maak een nieuw project met de instellingen van uw keuze, maar wanneer u daarom wordt gevraagd, selecteert u het Neem Kotlin-ondersteuning op checkbox.
In deze sectie implementeren we de volgende functionaliteit:
voer email in
veld-.Geroosterd brood
. RxBinding is een bibliotheek die het gemakkelijker maakt om een breed scala van UI-evenementen in Observables om te zetten, waarna je ze net als elke andere RxJava-gegevensstroom kunt behandelen.
We gaan wijzigingen in tekstwijzigingen monitoren door RxBinding's te combineren widget.RxTextView
met de afterTextChangeEvents
methode, bijvoorbeeld:
RxTextView.afterTextChangeEvents (enterEmail)
Het probleem met het behandelen van tekstwijzigingsgebeurtenissen als gegevensstromen is in eerste instantie zowel het voer email in
en enterPassword EditTexts
is leeg en we willen niet dat onze app reageert op deze lege staat alsof het de eerste gegevensuitwisseling in de stream is. RxBinding lost dit probleem op door een skipInitialValue ()
methode, die we gebruiken om elke waarnemer te instrueren de beginwaarde van hun stream te negeren.
RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue ()
Ik kijk naar de RxBinding-bibliotheek in meer detail in mijn RxJava 2 voor Android Apps-artikel.
.ontdendering ()
operatorOm de beste gebruikerservaring te bieden, moeten we alle relevante wachtwoord- of e-mailwaarschuwingen weergeven nadat de gebruiker klaar is met typen, maar voordat ze de Inschrijven knop.
Zonder RxJava, zou het identificeren van dit beperkte tijdvenster typisch vereisen dat we een timer
, maar in RxJava hoeven we alleen de ontdendering ()
operator naar onze datastream.
Ik ga de gebruiken ontdendering ()
operator om alle tekstwijzigingsgebeurtenissen uit te filteren die snel achter elkaar plaatsvinden, d.w.z. wanneer de gebruiker nog steeds bezig is met typen. Hier negeren we alle tekstwijzigingsgebeurtenissen die plaatsvinden binnen hetzelfde 400-milliseconde venster:
RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue () .debounce (400, TimeUnit.MILLISECONDS)
AndroidSchedulers.mainThread ()
De RxAndroid-bibliotheek AndroidSchedulers.mainThread
geeft ons een eenvoudige manier om over te schakelen naar de belangrijkste UI-thread van Android.
Omdat het alleen mogelijk is de gebruikersinterface van Android te updaten vanuit de hoofd UI-thread, moeten we zeker weten dat we deze thread gebruiken voordat we proberen waarschuwingen voor e-mail of wachtwoorden weer te geven, en voordat we onze Geroosterd brood
.
RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue () .debounce (400, TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ())
Om de gegevens te ontvangen die worden uitgezonden voer email in
, we moeten ons erop abonneren:
RxTextView.afterTextChangeEvents (enterEmail) .skipInitialValue () .debounce (400, TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) .subscribe
Uiteindelijk willen we dat onze app reageert op tekstwijzigingsgebeurtenissen door de invoer van de gebruiker te valideren, maar om dingen eenvoudig te houden, op dit punt ga ik gewoon een Geroosterd brood
.
Uw code zou er ongeveer zo uit moeten zien:
import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.widget.Toast import com.jakewharton.rxbinding2.widget.RxTextView import kotlinx.android.synthetic.main.activity_main. * import io.reactivex.android .schedulers.AndroidSchedulers importeer java.util.concurrent.TimeUnit class MainActivity: AppCompatActivity () override fun 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 (this, "400 milliseconds since last text change", Toast.LENGTH_SHORT) .show ()
Omdat we een aantal verschillende bibliotheken gebruiken, moeten we onze projecten openen build.gradle bestand en voeg RxJava, RxBinding en RxAndroid toe als projectafhankelijkheden:
afhankelijkheden implementatie fileTree (dir: 'libs', include: ['* .jar']) implementatie "org.jetbrains.kotlin: kotlin-stdlib-jdk7: $ kotlin_version" implementatie 'com.android.support:design:28.0. 0-alpha1 'implementatie' com.android.support:appcompat-v7:28.0.0-alpha1 'implementatie' com.android.support.constraint: constraint-layout: 1.1.0 '// Voeg de RxJava-afhankelijkheid // implementatie toe' io.reactivex.rxjava2: rxjava: 2.1.9 '// Voeg de RxAndroid-afhankelijkheid // implementatie toe' io.reactivex.rxjava2: rxandroid: 2.0.2 '// Voeg de RxBinding-afhankelijkheid // implementatie toe com.jakewharton.rxbinding2: rxbinding: 2.1.1 '
U kunt dit deel van uw project testen door het op uw fysieke Android-smartphone of -tablet of op Android Virtual Device (AVD) te installeren. Selecteer de voer email in
Tekst bewerken
en begin met typen; een Geroosterd brood
zou moeten verschijnen als u stopt met typen.
Vervolgens moeten we enkele basisregels vastleggen over het soort invoer dat onze toepassing zal accepteren, en dan de invoer van de gebruiker tegen dit criterium controleren en indien nodig een foutmelding weergeven.
Het e-mailadres of wachtwoord van de gebruiker controleren is een proces in meerdere stappen, dus om onze code leesbaarder te maken, ga ik al deze stappen combineren in hun eigen transformatiefunctie.
Hier is het begin van de email valideren
transformatiefunctie:
// Definieer een ObservableTransformer. Invoer en uitvoer moeten een tekenreeks // private val validateEmailAddress = ObservableTransformer zijnobserveable -> // Gebruik flatMap om een functie toe te passen op elk item dat wordt geëmitteerd door de Waarneembare // observeable.flatMap // Elke spaties trimmen aan het begin en einde van de invoer van de gebruiker // Observable.just (it) .map it.trim () // Controleer of de invoer overeenkomt met het e-mailpatroon van Android //. filter Patterns.EMAIL_ADDRESS.matcher (it) .matches ()
In de bovenstaande code gebruiken we de filter()
operator om de uitvoer van de Observable te filteren op basis van of deze overeenkomt met die van Android Patterns.EMAIL_ADDRESS
patroon.
In het volgende deel van de transformatiefunctie moeten we specificeren wat er gebeurt als de invoer niet overeenkomt met de E-MAILADRES
patroon. Standaard activeert elke onherstelbare fout een oproep naar OnError ()
, die de datastream beëindigt. In plaats van de stream te beëindigen, willen we dat onze app een foutmelding weergeeft, dus ik ga deze gebruiken onErrorResumeNext
, die het waarneembare instrueert om op een fout te reageren door de controle door te geven aan een nieuwe waarneembaar in plaats van aan te roepen OnError ()
. Hierdoor kunnen we ons aangepaste foutbericht weergeven.
// Als de invoer van de gebruiker niet overeenkomt met het e-mailpatroon, gooi dan een fout // .singleOrError () .onErrorResumeNext if (it is NoSuchElementException) Single.error (Uitzondering ("Vul een geldig e-mailadres in")) else Single.error (it) .toObservable ()
De laatste stap is om deze transformatiefunctie toe te passen op de e-mailgegevensstroom, met behulp van de .componeren()
operator. Op dit punt, jouw MainActivity.kt zou er ongeveer zo uit moeten zien:
import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.util.Patterns import io.reactivex.Observable import io.reactivex.ObservableTransformer import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.activity_main. * import java.util.concurrent.TimeUnit import com.jakewharton.rxbinding2.widget.RxTextView klasse MainActivity: AppCompatActivity () override fun 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, // Zorg ervoor dat we zitten in de hoofd UI-thread van Android // TimeUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) .compose (validateEmailAddress) .compose (retryWhenError passwordError.error = it.message) .subscribe () // If de app loopt een fout tegen en probeer het opnieuw // private inline fu n retryWhenError (crossinline onError: (ex: Throwable) -> Unit): ObservableTransformer= ObservableTransformer observeable -> observeable.retryWanneer errors -> // Gebruik de operator flatmap () om alle emissies af te vlakken tot één Observable // errors.flatMap onError (it) Observable.just ("") / / Definieer een ObservableTransformer, waar we de e-mail validatie uitvoeren // private val validateEmailAddress = ObservableTransformer observeable -> observeable.flatMap observeerbaar.just (it) .map it.trim () // Controleer of de gebruikersinvoer overeenkomt met het e-mailpatroon van Android // .filter Patterns.EMAIL_ADDRESS.matcher (it) .matches ( ) // Als de invoer van de gebruiker niet overeenkomt met het e-mailpatroon, gooi dan een fout // .singleOrError () .onErrorResumeNext if (it is NoSuchElementException) Single.error (Uitzondering ("Vul een geldig e-mailadres in") )) else Single.error (it) .toObservable ()
Installeer dit project op uw Android-apparaat of AVD en u zult zien dat het e-mailgedeelte van de Inschrijven scherm controleert nu uw invoer met succes. Probeer iets anders in te voeren dan een e-mailadres en de app waarschuwt u dat dit geen geldige invoer is.
Op dit punt hebben we een volledig functionerende voer email in
veld- en implementatie voer wachtwoord in
is meestal slechts een geval van het herhalen van dezelfde stappen.
In feite is het enige grote verschil dat onze validatePassword
transformatie functie moet controleren op verschillende criteria. Ik ga aangeven dat de wachtwoordinvoer van de gebruiker ten minste 7 tekens lang moet zijn:
.filter it.length> 7
Na het herhalen van alle voorgaande stappen, is het voltooid MainActivity.kt zou er ongeveer zo uit moeten zien:
import android.support.v7.app.AppCompatActivity import android.os.Bundle import android.util.Patterns import io.reactivex.Observable import io.reactivex.ObservableTransformer import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.activity_main. * import java.util.concurrent.TimeUnit import com.jakewharton.rxbinding2.widget.RxTextView klasse MainActivity: AppCompatActivity () override fun onCreate (savedInstanceState: Bundle?) super.onCreate (savedInstanceState) setContentView (R.layout.activity_main) // Reageren op tekstwijzigingsgebeurtenissen in enterEmail // RxTextView.afterTextChangeEvents (enterEmail) // Sla de initiële, lege status //EVCInitialValue () van EnterEmail over) // Transformeer de gegevens die worden geëmitteerd / / .map emailError.error = null // Converteer de gebruikersinvoer naar een tekenreeks // it.view (). text.toString () // Negeer alle emissies die optreden binnen een tijdsperiode van 400 milliseconden // .debounce (400 , // Zorg ervoor dat we in de hoofd UI-thread van Android staan // Tim eUnit.MILLISECONDS) .observeOn (AndroidSchedulers.mainThread ()) // Pas de transformatiefunctie validateEmailAddress // .compose toe (validateEmailAddress) // Pas de functie retryWhenError-transformatie // .compose toe (retryWhenError emailError.error = it.message) .subscribe () // Spoelen en herhalen voor de 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 () // Als de app een fout aantreft, probeer dan opnieuw / / private inline fun retryWhenError (crossinline onError: (ex: Throwable) -> Unit): ObservableTransformer= ObservableTransformer observeerbaar -> observeable.retryWanneer errors -> /// Gebruik de operator flatmap () om alle emissies af te vlakken tot één Observable // errors.flatMap onError (it) Observable.just ("") // Definieer onze ObservableTransformer en geef aan dat de invoer en uitvoer een tekenreeks // private val validatePassword = ObservableTransformer moeten zijn observeable -> observeable.flatMap observeerbaar.just (it) .map it.trim () // Sta alleen wachtwoorden toe die minimaal 7 tekens lang zijn //. filter it.length> 7 // Als de wachtwoord is minder dan 7 tekens, gooi dan een fout // .singleOrError () // Als er een fout optreedt ... // .onErrorResumeNext if (het is NoSuchElementException) // Geef het volgende bericht weer in het wachtwoord Error TextInputLayout // Single. error (Uitzondering ("Uw wachtwoord moet uit 7 tekens of meer bestaan")) else Single.error (it) .toObservable () // Definieer een ObservableTransformer, waar we de e-mail validatie zullen uitvoeren // private val validateEmailAddress = ObservableTransformer observeable -> observeable.flatMap observeerbaar.just (it) .map it.trim () // Controleer of de gebruikersinvoer overeenkomt met het e-mailpatroon van Android // .filter Patterns.EMAIL_ADDRESS.matcher (it) .matches ( ) // Als de invoer van de gebruiker niet overeenkomt met het e-mailpatroon ... // .singleOrError () .onErrorResumeNext if (it is NoSuchElementException) //// Geef het volgende bericht weer in de emailError TextInputLayout // Single.error ( Uitzondering ("Voer een geldig e-mailadres in")) else Single.error (it) .toObservable ()
Installeer dit project op je Android-apparaat of AVD en experimenteer met typen in de voer email in
en voer wachtwoord in
velden. Als u een waarde invoert die niet voldoet aan de vereisten van de app, wordt het bijbehorende waarschuwingsbericht weergegeven, zonder je moet op de Inschrijven knop.
Je kunt dit complete project downloaden van GitHub.
In dit artikel hebben we gekeken hoe RxJava kan helpen bij het oplossen van de echte problemen die u tegenkomt bij het ontwikkelen van uw eigen Android-applicaties, door RxJava 2.0, RxBinding en RxAndroid te gebruiken om een Inschrijven scherm.
Bekijk voor meer achtergrondinformatie over de RxJava-bibliotheek ons artikel Aan de slag met RxJava 2.0.