RxJava 2 för Android Apps RxBinding och RxLifecycle

RxJava är en av de mest populära biblioteken för att få reaktiv programmering till Android-plattformen, och i den här tredelade serien har jag visat dig hur du börjar använda detta bibliotek i dina egna Android-projekt.

I Komma igång med RxJava 2 för Android såg vi på vad RxJava är och vad den har att erbjuda Android-utvecklare innan du skapar en Hello World-app som visade de tre huvudkomponenterna i RxJava: en Märkbar, en Observatör, och en prenumeration.

I de reaktiva programmeringsoperatörerna i RxJava 2 handledning såg vi på hur man utför komplexa dataomvandlingar med hjälp av operatörer, och hur man kan kombinera Operatörs och Schemaläggares för att slutligen göra multithreading på Android en smärtfri upplevelse.

Vi berörde också RxAndroid, ett bibliotek som är särskilt utformat för att hjälpa dig att använda RxJava i dina Android-projekt, men det finns mycket mer att utforska i RxAndroid. Så, i det här inlägget kommer jag att fokusera enbart på bibliotekets RxAndroid-familj.

I likhet med RxJava, RxAndroid genomgick en omfattande översyn i sin version 2 release. RxAndroid-laget bestämde sig för att modularisera biblioteket och flyttade mycket av sin funktionalitet till dedikerade RxAndroid-tilläggsmoduler.

I den här artikeln ska jag visa dig hur du installerar och använder några av de mest populära och kraftfulla RxAndroid-modulerna, inklusive ett bibliotek som kan göra lyssnare, hanterare och TextWatchers en sak av det förflutna genom att ge dig förmågan att hantera några Android-användarhändelse som en Märkbar.

Och eftersom minnesläckor orsakade av ofullständiga prenumerationer är den största nackdelen med att använda RxJava i dina Android-appar, visar jag dig också hur man använder en RxAndroid-modul som hanterar prenumerationsprocessen för dig. I slutet av den här artikeln kommer du att veta hur du använder RxJava i någon Aktivitet eller Fragment, utan att riskera att stöta på några RxJava-relaterade minnesläckor.

Skapa mer reaktiva Android-gränssnitt

Att reagera på gränsöverskridande händelser som kranar, swipes och textinmatning är en grundläggande del av att utveckla nästan alla Android-appar, men hantering av Android-gränssnittsevenemang är inte särskilt okomplicerat.

Du reagerar vanligtvis på UI-händelser med hjälp av en kombination av lyssnare, hanterare, TextWatchers, och eventuellt andra komponenter beroende på vilken typ av användargränssnitt du skapar. Var och en av dessa komponenter kräver att du skriver en betydande mängd boilerplate-kod och för att göra saken värre finns det ingen konsekvens i hur du implementerar dessa olika komponenter. Till exempel hanterar du OnClick händelser genom att implementera en OnClickListener:

Knappknapp = (Knapp) findViewById (R.id.button); button.setOnClickListener (new View.OnClickListener () @Override public void onClick (Visa v) // Utför ett visst arbete //);

Men det här är helt annorlunda än hur du implementerar en TextWatcher:

sista EditText namn = (EditText) v.findViewById (R.id.name); // Skapa en TextWatcher och ange att denna TextWatcher ska ringas när EditText innehåll ändras // name.addTextChangedListener (new TextWatcher () @Override public void beforeTextChanged (CharSequence s, int start, int count, int efter)  @ Överrota public void onTextChanged (CharSequence s, int start, int före, int räkna) // Utför något arbete // @Override public void afterTextChanged (Editable s) );

Denna brist på konsistens kan potentiellt lägga till mycket komplexitet i din kod. Och om du har UI-komponenter som beror på produktionen från andra UI-komponenter, gör dig redo för att bli ännu mer komplicerad! Även ett enkelt användningsfall - till exempel att be användaren att skriva in sitt namn i en Redigera text så du kan anpassa texten som visas i efterföljande TextViews-kräver inbäddade återkopplingar, som är notoriskt svåra att implementera och underhålla. (Några hänvisar till nestade återuppringningar som "återkoppling helvete.")

Det är uppenbart att ett standardiserat tillvägagångssätt för hantering av användarhändelser har potential att kraftigt förenkla din kod och RxBinding är ett bibliotek som anger att du bara gör det genom att tillhandahålla bindningar som gör att du kan konvertera alla Android Se händelse till en Märkbar.  

När du har konverterat en visnings händelse till en Märkbar, det kommer att avge sina användargränssnitt som dataflöden som du kan prenumerera på på samma sätt som du skulle prenumerera på någon annan Märkbar.

Eftersom vi redan har sett hur du skulle fånga en klickhändelse med hjälp av Android standard OnClickListener, låt oss se hur du skulle uppnå samma resultat med RxBinding:

importera com.jakewharton.rxbinding.view.RxView; ... Knappknapp = (Knapp) hittaViewById (R.id.button); RxView.clicks (button) .subscribe (aVoid -> // Gör något arbete här //);

Inte bara är detta tillvägagångssätt mer koncist, men det är en standardimplementation som du kan ansöka om alla gränssnittshändelser som inträffar i hela appen. Fånga textinmatning följer till exempel samma mönster som att fånga klickhändelser:

RxTextView.textChanges (editText) .subscribe (charSequence -> // Gör något arbete här //);

Ett exempelapp med RxBinding

Så du kan se exakt hur RxBinding kan förenkla appens användargränssnittskod, låt oss skapa en app som visar några av dessa bindningar i åtgärd. Jag ska också inkludera a Se Det är beroende av en annan Se, för att visa hur RxBinding förenklar att skapa relationer mellan användargränssnittskomponenter.

Den här appen kommer att bestå av:

  • en Knapp som visar a Rostat bröd när den tappas.
  • En Redigera text som upptäcker textändringar.
  • en Textview som uppdateras för att visa innehållet i Redigera text.

Projektinställningar

Skapa ett Android Studio-projekt med de inställningar du vill ha, och öppna din modulnivå build.gradle fil och lägg till den senaste versionen av RxBinding-biblioteket som ett projektberoende. För att hålla boilerplate-koden till ett minimum ska jag också använda lambdas, så jag har uppdaterat min build.gradle fil som stöder denna Java 8-funktionen:

tillämpa plugin: 'com.android.application' android compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig applicationId "com.jessicathornsby.myapplication" minSdkVersion 23 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner. AndroidJUnitRunner "// Aktivera Jack toolchain // jackOptions enabled true buildTypes release minifyEnabled false proguardFiles getDefaultProguardFile ('proguard-android.txt'), 'proguard-rules.pro' // Ange källkompatibilitet och målCompatibility JavaVersion.VERSION_1_8 // compileOptions sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 beroenden compile fileTree (dir: 'libs', inkluderar: ['* .jar']) androidTestCompile ('com.android.support.test.espresso : espressokärna: 2.2.2 ', uteslut grupp:' com.android.support ', modul:' support-annotations ') // Lägg till kärnan RxBinding bibliotek // kompilera' com.jakewharton.rxbinding: rxbinding: 0.4.0 "kompilera" com .android.support: appcompat-v7: 25.3.0 '// Glöm inte att lägga till RxJava och RxAndroid beroenden // kompilera' io.reactivex.rxjava2: rxandroid: 2.0.1 'compile' io.reactivex.rxjava2: rxjava: 2.0.5 "testCompile" junit: junit: 4.12 ' 

När du arbetar med flera RxJava-bibliotek kan det hända att du kan stöta på en Dubbla filer som kopierats i APK META-INF / AFPENDENCIES felmeddelande vid kompileringstid. Om du stöter på det här felet, är lösningen att undertrycka dessa dubbla filer genom att lägga till följande på din modulnivå build.gradle fil:

android packagingOptions // Använd "exkludera" för att peka på den specifika filen (eller filerna) som Android Studio klagar över // utesluta 'META-INF / rxjava.properties'

Skapa huvudaktivitetslayouten

Synkronisera dina Gradle-filer och skapa sedan en layout som består av a Knapp, en Redigera text, och a Textview:

  

Koda händelsebindningarna

Låt oss nu titta på hur du använder dessa RxBinding för att fånga olika UI-händelser som vår applikation behöver reagera på. För att börja med, förklara din import och definiera Huvudaktivitet klass. 

paketet com.jessicathornsby.myapplication; importera android.os.Bundle; importera android.support.v7.app.AppCompatActivity; importera android.widget.Button; importera android.widget.EditText; importera android.widget.TextView; importera android.widget.Toast; // Importera view.RxView-klassen, så att du kan använda RxView.clicks // import com.jakewharton.rxbinding.view.RxView; // Import widget.RxTextView så att du kan använda RxTextView.textChanges // import com.jakewharton.rxbinding.widget.RxTextView; public class MainActivity utökar AppCompatActivity @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); Knappknapp = (Knapp) findViewById (R.id.button); TextView textView = (TextView) findViewById (R.id.textView); EditText editText = (EditText) findViewById (R.id.editText); // Kod för bindningarna går här // // ... //

Nu kan du börja lägga till bindningar för att svara på UI-händelser. De RxView.clicks Metoden används för att binda klickhändelser. Skapa en bindning för att visa en rostat bröd när du klickar på knappen:  

 RxView.clicks (button) .subscribe (aVoid -> Toast.makeText (MainActivity.this, "RxView.clicks", Toast.LENGTH_SHORT) .show ();); 

Använd sedan RxTextView.textChanges () Metod att reagera på en textbyteshändelse genom att uppdatera Textview med innehållet i vårt Redigera text.

 RxTextView.textChanges (editText) .subscribe (charSequence -> textView.setText (charSequence);); 

När du kör din app kommer du att sluta med en skärm som följande.

Installera ditt projekt på en fysisk Android-smarttelefon eller -tablett eller en kompatibel AVD, och spendera sedan lite tid på att interagera med olika användargränssnitt. Din app ska reagera på att klicka på händelser och textinmatning som vanligt - och alla utan en lyssnare, TextWatcher eller återuppringning i sikte!


RxBinding för supportbibliotekets visningar

Medan det centrala RxBinding-biblioteket ger bindningar för alla UI-element som utgör den vanliga Android-plattformen, finns det även RxBinding-syskonmoduler som ger bindningar för de visningar som ingår som en del av Android: s olika supportbibliotek.

Om du har lagt till ett eller flera supportbibliotek i ditt projekt vill du normalt lägga till motsvarande RxBinding-modul.

Dessa syskonmoduler följer en enkel namngivningskonvention som gör det enkelt att identifiera motsvarande Android-supportbibliotek: varje syskonmodul tar bara stödbibliotekets namn och ersätter com.android med com.jakewharton.rxbinding2: rxbinding.

  • kompilera com.jakewharton.rxbinding2: rxbinding-recyclerview-v7: 2.0.0 '
  • kompilera 'com.jakewharton.rxbinding2: rxbinding-support-v4: 2.0.0'
  • kompilera 'com.jakewharton.rxbinding2: rxbinding-appcompat-v7: 2.0.0'
  • kompilera 'com.jakewharton.rxbinding2: rxbinding-design: 2.0.0'
  • kompilera 'com.jakewharton.rxbinding2: rxbinding-recyclerview-v7: 2.0.0'
  • kompilera 'com.jakewharton.rxbinding2: rxbinding-leanback-v17: 2.0.0'

Om du använder Kotlin i dina Android-projekt, finns det också en Kotlin-version tillgänglig för varje RxBinding-modul. För att komma åt Kotlin-versionen, lägg till -Kotlin till namnet på det bibliotek du vill arbeta med, så:

kompilera 'com.jakewharton.rxbinding2: rxbinding-design: 2.0.0'

blir:

kompilera 'com.jakewharton.rxbinding2: rxbinding-design-kotlin: 2.0.0'

När du har konverterat en Se händelse till en Märkbar, alla dessa händelser sänds som en dataström. Som vi redan har sett kan du prenumerera på dessa strömmar och sedan utföra vilken uppgift du behöver för den här speciella gränssnittshändelsen, som att visa en Rostat bröd eller uppdatera a Textview. Du kan dock också tillämpa någon av RxJavas enorma samling av operatörer till denna observerbara ström och till och med kedja flera operatörer tillsammans för att utföra komplexa omvandlingar på dina UI-händelser.

Det finns alltför många operatörer att diskutera i en enda artikel (och de officiella dokumenten listar alla operatörer ändå) men när det gäller att arbeta med Android UI-händelser, finns det några operatörer som kan komma särskilt användbara.

De debounce () Operatör

För det första, om du är orolig för att en otålig användare kan hoppa iväg vid ett UI-element flera gånger, eventuellt förvirra din app, kan du använda debounce () operatören för att filtrera bort eventuella användargränssnittshändelser som emitteras i snabb följd.

I följande exempel anger jag att den här knappen ska reagera på en OnClick händelse endast om det har funnits åtminstone ett gap på 500 millisekunder sedan föregående klickhändelse:

RxView.clicks (button) .debounce (500, TimeUnit.MILLISECONDS) .subscribe (aVoid -> Toast.makeText (MainActivity.this, "RxView.clicks", Toast.LENGTH_SHORT) .show (););

De publicera() Operatör

Du kan också använda publicera() operatör för att bifoga flera lyssnare i samma vy, något som traditionellt varit svårt att implementera i Android.

De publicera() Operatören konverterar en standard Märkbar in i en anslutningsbar märkbar. Medan en regelbunden observerbar början avger emitter så snart den första observatören abonnerar på den, kommer en anslutbar observerbar inte att avge något förrän du uttryckligen instruerar det, genom att tillämpa ansluta() operatör. Detta ger dig ett fönstret där du kan prenumerera flera observatörer, utan att det observerbara börjar att skicka ut objekt så snart den första prenumerationen äger rum.

När du har skapat alla dina prenumerationer, använd bara ansluta() operatören och den observerbara kommer att börja sända data till alla dess tilldelade observatörer.  

Undvik App-Breaking Memory Läckage

Som vi har sett i hela serien kan RxJava vara ett kraftfullt verktyg för att skapa mer reaktiva interaktiva Android-applikationer, med mycket mindre kod än vad du vanligtvis behöver för att få samma resultat med Java ensam. Det finns emellertid en stor nackdel med att använda RxJava i dina Android-applikationer - potentialen för minnesläckor orsakade av ofullständiga prenumerationer.

Dessa minnesläckor uppstår när Android-systemet försöker förstöra en Aktivitet som innehåller en körning Märkbar. Eftersom det observerbara springer, kommer dess observatör fortfarande att hålla en referens till verksamheten, och systemet kommer inte att kunna skräp samla denna aktivitet som ett resultat.

Eftersom Android förstör och återskapar Aktivitets varje gång enhetens konfiguration ändras kan din app skapa en dubblett Aktivitet varje gång användaren växlar mellan stående och liggande läge, samt varje gång de öppnar och stänger enhetens tangentbord.

Dessa aktiviteter kommer att hänga i bakgrunden, eventuellt aldrig att få skräp samlad. Eftersom aktiviteter är stora objekt kan detta snabbt leda till allvarlig minnehanteringsproblem, särskilt eftersom Android-smarttelefoner och surfplattor har begränsat minne till att börja med. Kombinationen av ett stort minne och begränsat minne kan snabbt resultera i en Slut på minne fel.

RxJava minnesläckor kan ha potential att utgöra kaos med programmets prestanda, men det finns ett RxAndroid-bibliotek som låter dig använda RxJava i din app utan att behöva oroa dig för minnesläckor.

RxLifecycle-biblioteket, utvecklat av Trello, tillhandahåller API för livscykelhantering som du kan använda för att begränsa livslängden för en Märkbar till livets livscykel Aktivitet eller Fragment. När denna anslutning har gjorts slutar RxLifecycle den observerbara sekvensen som svar på livscykelhändelser som uppträder i den observerbara tilldelade aktiviteten eller fragmentet. Det betyder att du kan skapa en observerbar som slutar automatiskt när en aktivitet eller ett fragment förstörs.

Observera att vi pratar om avslutande en sekvens och inte avstängning. Även om RxLifecycle ofta pratar om i samband med hanteringen av prenumerations- / unsubscription processen, tekniskt det avbryter inte en observatör. I stället avslutar RxLifecycle-biblioteket den observerbara sekvensen genom att emittera antingen onComplete () eller onerror () metod. När du avslutar prenumerationen slutar observatören ta emot meddelanden från dess observerbara, även om det observerbara fortfarande är emitterande föremål. Om du specifikt behöver avbryta beteende, så är det något du behöver implementera dig själv.

Användning av RxLifecycle

För att använda RxLifecycle i dina Android-projekt, öppna din modulnivå build.gradle fil och lägg till den senaste versionen av RxLifeycle-biblioteket, plus RxLifecycle Android-biblioteket:

beroenden ... kompilera 'com.trello.rxlifecycle2: rxlifecycle: 2.0.1' compile 'com.trello.rxlifecycle2: rxlifecycle-android: 2.0.1'

Då, i Aktivitet eller Fragment där du vill använda bibliotekets livscykelhanterings API, förlänger du heller RxActivity, RxAppCompatActivity eller RxFragment, och lägg till motsvarande importdeklaration, till exempel:

importera com.trello.rxlifecycle2.components.support.RxAppCompatActivity; ... allmän klass MainActivity utökar RxAppCompatActivity 

När det gäller att binda en Märkbar till livets livscykel Aktivitet eller Fragment, du kan antingen ange livscykelhändelsen där den observerbara bör avslutas, eller du kan låta RxLifecycle-biblioteket bestämma när det ska avsluta den observerbara sekvensen.

Som standard avslutar RxLifecycle en observerbar i den kompletterande livscykelhändelsen till den där abonnemanget inträffade, så om du prenumererar på en observerbar under din aktivitet s onCreate () metod kommer RxLifecycle att avsluta den observerbara sekvensen under den aktiviteten onDestroy () metod. Om du prenumererar under en Fragment's onAttach () metod, så kommer RxLifecycle att avsluta denna sekvens i onDetach () metod.

Du kan lämna detta beslut upp till RxLifecycle, genom att använda RxLifecycleAndroid.bindActivity:

Märkbar myObservable = Observable.range (0, 25); ... @Override public void onResume () super.onResume (); myObservable .compose (RxLifecycleAndroid.bindActivity (livscykel)) .subscribe (); 

Alternativt kan du ange livscykelhändelsen där RxLifecycle ska avsluta en Märkbar sekvens med användning av RxLifecycle.bindUntilEvent.

Här specificerar jag att den observerbara sekvensen bör avslutas i onDestroy ():

@Override public void onResume () super.onResume (); myObservable .compose (RxLifecycle.bindUntilEvent (livscykel, ActivityEvent.DESTROY)) .subscribe ();  

Arbetar med Android Marshmallow Tillstånd

Det slutliga biblioteket vi ska titta på är RxPermissions, vilket var utformat för att hjälpa dig att använda RxJava med den nya behörighetsmodellen som introducerades i Android 6.0. I det här biblioteket kan du också utfärda en tillståndsförfrågan och hantera tillståndsresultatet på samma plats, istället för att begära tillståndet på ett ställe och sedan hantera resultaten separat, i Activity.onRequestPermissionsResult ().

Börja med att lägga till RxPermissions-biblioteket till din build.gradle fil:

kompilera 'com.tbruyelle.rxpermissions2: rxpermissions: 0.9.3@aar'

Skapa sedan en RxPermissions-instans:

RxPermissions rxPermissions = nya RxPermissions (detta);

Du är då redo att börja göra tillståndsförfrågningar via biblioteket RxPermissions, med följande formel:

rxPermissions.request (Manifest.permission.READ_CONTACTS) .subscribe (granted -> if (granted) // Tillståndet har beviljats ​​// else // Tillståndet har nekats //);

Var du utfärdar din behörighetsförfrågan är avgörande, eftersom det alltid finns en chans att hosting Aktivitet kan förstöras och sedan återskapas medan behörighetsdialogen är på skärmen, vanligtvis på grund av en konfigurationsändring som användaren flyttar mellan stående och liggande lägen. Om det här inträffar kanske din prenumeration kanske inte återskapas, vilket innebär att du inte kommer att prenumerera på RxPermissions observerbara och kommer inte att få användarens svar på dialogrutan för tillståndsförfrågan. För att garantera att din ansökan får användarens svar, alltid anropa din förfrågan under en initialiseringsfas, t.ex. Activity.onCreate ()Activity.onResume (), eller View.onFinishInflate ().

Det är inte ovanligt att funktioner kräver flera behörigheter. Om du t.ex. skickar ett sms-meddelande kräver det vanligtvis att din app har SKICKA SMS och READ_CONTACTS behörigheter. Biblioteket RxPermissions ger en kortfattad metod för att utfärda flera behörighetsförfrågningar och sedan kombinera användarens svar till en enda falsk (en eller flera behörigheter nekades) eller Sann (alla behörigheter beviljades) svar som du då kan reagera på.

RxPermissions.getInstance (this) .request (Manifest.permission.SEND_SMS, Manifest.permission.READ_CONTACTS) .subscribe (granted -> if (granted) // Alla behörigheter beviljades // else // En eller flera behörigheter var nekad//  );

Du vill vanligtvis utlösa en tillståndsförfrågan som svar på en användargränssnittshändelse, till exempel användaren som knackar på ett menyalternativ eller en knapp, så RxPermissions och RxBiding är två bibliotek som fungerar särskilt bra tillsammans.

Hantering av UI-händelsen som observerbar och med tillståndsförfrågan via RxPermissions gör att du kan utföra mycket arbete med några få rader kod:

RxView.clicks (findViewById (R.id.enableBluetooth)) .compose (RxPermissions.getInstance (this) .ensure (Manifest.permission.BLUETOOTH_ADMIN)) .subscribe (granted -> // "enableBluetooth" -knappen har blivit klickad / /);

Slutsats

Efter att ha läst den här artikeln har du några idéer om hur man skär mycket kedjekodskod från dina Android-appar, genom att använda RxJava för att hantera alla programmets användargränssnittshändelser och utfärda dina tillståndsförfrågningar via RxPermissions. Vi tittade också på hur du kan använda RxJava i alla Android Aktivitet eller Fragment, utan att behöva oroa sig för minnesläckor som kan orsakas av ofullständiga prenumerationer.

Vi har undersökt några av de mest populära och användbara RxJava- och RxAndroid-biblioteken i den här serien, men om du är angelägen om att se vad mer RxJava har att erbjuda Android-utvecklare, kolla in några av de många andra RxAndroid-biblioteken. Du hittar en omfattande lista över ytterligare RxAndroid-bibliotek över på GitHub.

Under tiden kan du kolla in några av våra andra Android-utvecklingsposter här på Envato Tuts+!

  • Kom igång med Retrofit 2 HTTP-klient

    Retrofit är en typsäker HTTP-klient för Android och Java. Retrofit gör det enkelt att ansluta till en REST webbtjänst genom att översätta API till Java ...
    Chike Mgbemena
    Android SDK
  • Introduktion till Android saker

    I december 2016 släppte Google en uppdaterad version av Android-operativsystemet med ett alternativ för devs att använda en avvecklad version för ansluten ...
    Paul Trebilcox-Ruiz
    Android
  • Hur man ringer och använder SMS i Android Apps

    I den här handledningen läser du om Android Telefoni och SMS API. Du lär dig hur man ringer ett samtal från din app och hur man övervakar telefonsamtalshändelser, ...
    Chike Mgbemena
    Android SDK
  • 6 Gör och gör inte för en fantastisk Android användarupplevelse

    De mest populära Android-appar har något gemensamt: de ger alla en bra användarupplevelse. I det här inlägget delar jag några tips som hjälper din app ...
    Jessica Thornsby
    Android SDK