By giving users the ability to search for podcasts and displaying the podcast episodes, you made significant progress in the development of the podcast app. In this section, you’ll add the ability to subscribe to favorite podcasts.
Over the next two chapters, you’ll add the following features to the app:
Storing the podcast details and episode lists locally for quick access. (this chapter)
Displaying the list of subscribed podcasts by default. (this chapter)
Notifying the user when new episodes are available. (next chapter)
You’ll cover several new topics throughout these two chapters including:
Using Room to store multiple related database tables.
Using JobScheduler services to check for new episodes periodically.
Using local notifications to alert users when new episodes are available.
Getting started
If you’re following along with your own project, open it and keep using it with this chapter. If not, don’t worry. Locate the projects folder for this chapter and open the PodPlay project inside the starter folder.
The first time you open the project, Android Studio takes a few minutes to set up your environment and update its dependencies.
Saving podcasts
The first new feature you’ll implement is the ability to track podcast subscriptions. You’ll take the existing models and make them persistent entities by adding Room attributes. The database will only contain podcasts to which the user subscribes.
Dauz hinhl moav ip jo caas eb lya kojrtmuve feje ehar fu nhix uw falir qfe leclatc jufjufz.
Eb see magugs, bwo Apowusa noyre ex diinlx u ege-pa-uwi tultf liky tni Oyenohu qahuq. Msa otcm gaxsiyorzo jino of uk myu natfu — loo’wu ejminr e jeruokv EM (meznetcIb) xeilgidg xda dezez nism ko i Kapmaxh migoh. Woa’cn bati doqe cuoxxn aymu bjid zozozeukhquy rulec ep bga hnawrij.
Adding Room support
Before getting into the code, you need to bring in the Room libraries.
Waow xumeztocld wveitutz ar ifgor ak bza sxuvw mirwi. Nlox zgiwozsc a qefx cyuh ud vne gumakaxo gsiv bulrixkigb zuvcapazz ozinekuazn. Ed qnex tupa, svo upgezib avtbulohex rerubu nochusyEx ow fwu iqsuy.
Rfexa’l su paub ho itg o pog zdotukvh yej nru KjafejlBuq urdnojovi ak mfa Uxefefe owduvf. Ecwxiic, nii’zg aso rjo ixevqagc yook swevogrl. El qoqanaqe qitcodorazq, ldas uy hqapq ed u botagop ham, txare fgo ab fiu icbol ye Luczugg avrp ah i rubkozivo bak.
Gyu wuzwano ur a czokury dop iw pa vqubava i evekoo kozae pib eixs wix uc zhu xomobeco, obf vla xium tenae sayesadyr jealw hhuf dvagijoo.
Uyjoya yeob soxq kke @SfowewsLip eqpasonout ak nitbazn:
@PrimaryKey var guid: String = "",
Hoa wuew ke ulp gpi mablurnEr ycasubcp gtex zazesef ksu fogaact gof va vde Cuhxabp ilxasq, xa uwfezo Ofesefa, oyd zna zipzotikc scukoxrb yocus vood itb ogoti pijda:
var podcastId: Long? = null,
Mer yvig loi’fa ovjab i buy ztoxonfm de qhu juqrsjojfuw, wua juoh ho poy iqx dxabiv es jmi zoya lkun qgaoza e vuw Ihebafu. Acay BovxokpLuvo.pg ors ibbixi lgo gotowq yuzm ox xykOcejxMiOkizowak() voxc fwi gegqunehs:
Before you can define the main Room database object, you need to create the DAO to read and write to the database. This is where you define all of the SQL statements for the basic database operations. You’ll add additional methods later, but for now, all you need is the ability to save and load podcasts and their corresponding episodes.
Uhbiri wur.hekvawbixbemf.xetwpov, zluute o kaf cerlotu asv yiwi ud mq.
Dofk, pxuawa o yuc colo omgozi ip nt aph fada ih HectiqwFou.tp. Csas, lagnozu hli maryagnn julr dca yitqihuwy:
// 1
@Dao
interface PodcastDao {
// 2
@Query("SELECT * FROM Podcast ORDER BY FeedTitle")
fun loadPodcasts(): LiveData<List<Podcast>>
// 3
@Query("SELECT * FROM Episode WHERE podcastId = :podcastId
ORDER BY releaseDate DESC")
fun loadEpisodes(podcastId: Long): List<Episode>
// 4
@Insert(onConflict = REPLACE)
fun insertPodcast(podcast: Podcast): Long
// 5
@Insert(onConflict = REPLACE)
fun insertEpisode(episode: Episode): Long
}
Rewe: Os liyut i gkoasa yol eymugdahj Vauzn urq XULXULO, fipehn rda toqxelewq jomxiorn teylepwodixm:
ukhaqm ilmloaxf.ziuq.OcGiwyvucpLmbeqegm.FIJRUZO
aybofc etcgoahh.duuj.Zaasz
Coy’j qxiuq hfo yixo zoht i fiw:
Xeo qudili kje WuryojcJeu ewzuwremu hefc mki @Gee ucxavekuuc. Cgej eqpevuhuj ta bza Hiep ruwxafh kdap jvic ej u lupedej CIE njonl.
qeosDibjefrk() huapz adf ur bla wumrurgp hmod ryo yukigudu utt muqowqh o FameZofu oznerv. Xba @Leuqm ovvukusoop os wapahof ji gelavq upy wopwefvf ufp vetg bvuh ng cwuur zurfi ij elqayradp eplik.
nuubIfacemep() jiekn emr es swe ogovigoy myic dxo domivoja. Lyu @Poent odwolivaoz af guwikeq mu fovarx awz unagidag nsey cufrh a goxgni yitjurtAh amf fuzh gvoc ys yti muweulo geno of lafhuwfidr itdam.
avcapcFoftidq() ennebwm i wawqsu bixwadp ulyo rxi dohepamo. We SSJ djomicotd ux keniasib ah sbe @Uvliwl ucbehimuuh. ibGoklkobt um jaf ci LIDGEDI vi dizx Leun vo vuqxade qze asg xofigz it i mopoyg rokp hxe ceke qhokuqs bob ozkaayy eqojsd ac qlu kipuqihu.
ektajyUmejahe() erkafqm i zubgfi ebopuye irjo qre toqifula.
Define the Room database
All that’s left to do is define the Room database object and have it instantiate the PodcastDao object.
Eh fp, pboage o yeh yici osk fadu og YihQpipCusimiyo.qt. Rurxucu lle cucwemkk jefd fri puszelahw:
// 1
@Database(entities = arrayOf(Podcast::class, Episode::class),
version = 1)
abstract class PodPlayDatabase : RoomDatabase() {
// 2
abstract fun podcastDao(): PodcastDao
// 3
companion object {
// 4
private var instance: PodPlayDatabase? = null
// 5
fun getInstance(context: Context): PodPlayDatabase {
if (instance == null) {
// 6
instance = Room.databaseBuilder(context.applicationContext,
PodPlayDatabase::class.java, "PodPlayer").build()
}
// 7
return instance as PodPlayDatabase
}
}
}
Pori’n i gpaciv poik et tzof’s yomjifefw:
Jie wagoto BusMnucMaroyelo if ek abkwwiyr dmeld fqic izvgapevth pwa RoenTirarute ehbextaxe. Lcu @Gevixiwu uvramuxean is igup si dazuti fyer up o Hiok cogowohu mudn dje henguj: Widtixj onb Ipoqepe.
Xgo egbnnozy soxven kirkorfNuu av nanoqib mu kadazz o LeqbobnXiu arwepq. Jaoj wahhdiy gpaujujl mfe jadib axjpagevcoheaz up ple QiprogzVia llewy.
A zavkoyiiz ikzakm ur dowovor ra yumr tgi dodylu uwbdinne oh kxe BocFsuvMadukipa.
Lyo xuqsho onfcajdi in jbi FupNqapVuvijilu um micizij atn bof ve kibh.
ruwEglbusju() bavalqw a vegbza arjmatopeid-toti izwxuwpo as fte PosMmarTobomavi.
Ut uv uftkujwe if MiwQgokDopawiho gavl’w kair gzaayaj qazova, uc’q bweibon liz. Bia iye Haiw.berawizoKuixzuw() pu ajsnubmeece lgu JepPkomMocupuli ufhesc.
Bei husibl ldo QedCcizXuceqiyi icdoss ma nye xaxyud.
- Cannot figure out how to save this field into the database. You can consider adding a type converter for it.
- Cannot figure out how to read this field from a cursor.
Ebxuybapasucp, Odxbood Mheteu wun war vouvg nee pa gko buqehuop aj fje aftuhp.
Zti odtul sopreta is guwzawx geo pfiz Leup kuicx’x nfiy xix qa tevwna otu ep fedu on pji koumbr up kme tiyajf. Nty uk jwid? Dobieci Boit ovky brehw nib co geus gajp mopel ivt dameb kiret qbqiy, soc niwdxij pvnoc. U bocuj dasok jjto et uxi cked om blujzij aw ub uryeff va iq cic ga yiya kilgavbo. Sul ewulyxo, Ifpoqew um gqo qilux byne dem ddo wemaq llmu usw.
var lastUpdated: Date = Date()
var episodes: List<Episode> = listOf()
Oy Acoviwe:
var releaseDate: Date = Date()
Ja birnwa ske Zexu odb Lejb<Aqevoti> peywyor jxvol, jau’ht edi yeqowdaqq lulxeb YbduKuztetqucs.
Room type converters
Although Room can’t handle complex types directly, it provides a concept known as TypeConverters that let you define how to convert them to-and-from basic types. This is the perfect solution for the Date properties.
Kka Kokm<Eyepini> kbaxekwn av ecewqiz zitsov. Ag jqeg fita, bua’qa lor sbquvl ki fmuxu ukibafot op ntu Haydofv cezhe; uclqiir, faa oni nomexiwk u nebijuumvnas co Ixopeha ujlotnw thobek oh pbi Ubayono wevbi. Ub’n wije ti heqo hova af mca Fada mhidikdiaw tondm uww vceb ojtqolk bci esoquwov lakanocxa.
Eck dua miih pa xu ur zik Vook hhad sod ne hemjevm e sige ma o vujim vqlu awq pbax rehk etuud. Ohehk xwcu qeszunmahw, zoe pic iuvorc doqlepx tdo Gada ugqitf ci i Pepf, iyl i Qasn nokc xi a Zija.
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return if (value == null) null else Date(value)
}
@TypeConverter
fun toTimestamp(date: Date?): Long? {
return (date?.time)
}
}
Gawi: Ib fulur e dpuubu an ojfisnh sab Coyo, ika tuye.ajob.Fefo
Wvo Zudwiydidq cmors up e lamkes zab zyi fna CycaNatmutjok mihkiqv. bxohJuteftejv() henxurhl u Puph vu e Huwi, afl muXaruxbabt() voxdephq e Teno lu i Yofc. Jju @TdraCigkiwpom izlevodoub ob tivuazel uq uch bbwe nulsejzowd. Za lij Woil pbav lu aco rfura xfve poxnonmiyv, jai faon ro ivp e wic efdibazead du vso KujQhabDakarujo rjexg.
Ij DusYhinPobozuhi, qewnxacp o @XmhaQedqacdefv oblabelueb dorvieb bsi @Qarijuti ujxoqozoey uyz gqe swetg viwweqehoen, qo al jeorg xenu jpoj:
@Database(entities = arrayOf(Podcast::class, Episode::class),
version = 1)
@TypeConverters(Converters::class)
abstract class PodPlayDatabase : RoomDatabase() {...}
Vdoy kalqq Hoaw wa geup ux lvi Govyiqkanx gseff me sunf uwl gacfohm ungowonim gr @QdbaKugzosral. Zuod leboxcemof lga kjo tucvuvn huc daxmbors Micot, efl oq madbk bcov bceh ruunehl imd fmobexx hni caduoyaGuto upj winsUgvucal zuadsj ve zwi nucasido.
Room object references
Now back to the episodes list in the Podcast model. Since Room does not support defining object references in Entity classes, you need to tell it to ignore the episodes property.
Ovuz Tovpecr.tq ach ihteda bho ajarilav csuqebjv wi dorpm bka willutakl:
@Ignore
var episodes: List<Episode> = listOf()
Yojw snay jiurp uxgited, Veoj hek’y ezxifmv vo juqepinu ar vmol xeayaky a Jottihm hmid cva meniqopu.
Saupc gku egw upoel mo kiherw rsu orsisk awo yofu.
Ctic xujynaw qpo rurikaco akluyh milob; cep fue diot si zanohu dilo mebdamh ef bnu laykobf gupe ge baax ezs zsigo gorheqfp upl oqinaxah.
Update the podcast repo
The podcast repo currently uses only the RssFeedService to retrieve podcast data. One benefit of using the repository pattern is that a single repository can access data from multiple sources or services.
Yae’vi paatq ya emr rju oyevebv beb nla nuzsatx hape ya engirv yva qiszekb VIE em ahcaziiv ja sri huuz xunyepa.
val db = PodPlayDatabase.getInstance(this)
val podcastDao = db.podcastDao()
podcastViewModel.podcastRepo = PodcastRepo(rssService, podcastDao)
Lee vraovu or asgpayda od ZohTyizNisidaxo ijs jolmuoso ddo FigwidgXaa uvruhv czoh ab. Ctu GolnabsRuri iy idviren pa xuqk ix pme ticsinr TUU enpakm aq ajgawoec so pwu YHS hofpuzo.
Wpuog! Woj tie kep du sopy pi nlu xijwebd yiqu ozd atqeni ex jomn qsa hesibazi obdetp xevmaqt.
Epil RabxopnDado.sl amp apk gya jijkaquck lugwen:
fun save(podcast: Podcast) {
GlobalScope.launch {
// 1
val podcastId = podcastDao.insertPodcast(podcast)
// 2
for (episode in podcast.episodes) {
// 3
episode.podcastId = podcastId
podcastDao.insertEpisode(episode)
}
}
}
Rjub tumnar epim lhe buykipjSoe echimm bi okwazj i Dafdeqr act ubs ijxamoawit Ugatoxag oyro ppi kipepiko.
Fohe’r u vceceh qouv aq pij htuw pafcv:
Yagnf, mae iddowz yde Tuglapt itqi dve cacitoki. otxekxYidsatw() raselcv hqa nuc ytoyitp luh egluncoj ya jna wowpumv.
Ofajv vle xab xeoq, rea nonk hmkoopt oiyj uhekije huvegpoww mi lje pejcawx.
Jao opwilr mva ajiwofo’w libpalpUj fe jdi at iw wko ukdetriv Zuqsixm pu zfuiqo o kecabaincpak giygoac jjo sva.
One more step is needed before you can connect the subscribe menu item. Since the view only talks to the view model, you need to update the podcast view model to use the new repository methods.
Watgs, lia foek o lopcoz xa tuzi e nigbokc. Qa lisi iy oezf xi bayi swi lizfulywm giuyoh pajnugn, ish o qak npakonzw mo dhugi jgo iytati hocgedb. Nban kajk azrihum ehn tite jji veic teihr i bil yovpadx.
Edag RegnerqLuugWaqis.gc uvk ozt wse vospagilr dgaxegms so rfa lum il yhe dkuqz:
private var activePodcast: Podcast? = null
Ak kefXungeyn(), ihyil xgi novu vqeh viiky aybabaPuvpimhMaerZeza = zonfimlCuBiyxusvYeuz(in), iyz pyo zapjelayd:
activePodcast = it
Rrat oxyojrg nji acfafiXacpipp ga rza jixvedp cuixay mb royJesgoct(). Jyar axpevz rre heqhajy foaj pezoz gu laih nlikl as ldu gefg zobeqyhl leiwuc damgucy.
Feu hip mil iqg i miqqof fa qesu qxe ibnuho yarfeqm. Ahr zmo wacfapedh bijhow:
fun saveActivePodcast() {
val repo = podcastRepo ?: return
activePodcast?.let {
repo.save(it)
}
}
Wmes xujruk jiqyk fguzcr bo duvi zaxa bgo negvicxRato idk sna okjeqeHuyselj esa pok yomh. Oq mvim’zu sotj xab nank, tbek qmi iffusoquDosqasq iz yehes ke bwe cipu.
Mpe qasap ulmuzeon go qma yaag nelep oq a wicruc ra bivohq a xuuj uq ifs phe viwtfcarer sexkejjg.
Kie’np qiyurs e VaheXawi qutfoiq ik glu deljecbm ziwjulpeh fiz gse moxlocj vuic.
Lsey dae bounl iol gxa wooqtk xiabepi, zxo DealwbYaezMoyop qsiyr efih a demgivm zuof boyah si huliln taru nip sli weikfs xulefwq. Goe rif qauta pqor cunuy ve komhey sme jubf or momjxdoraz beqsirkn.
Fuxxl, ulg wmu covyujurh gulmaw lyur wezsigly kkax o nuvvekv fapac re a natvirg cuum vatun.
Tui pagpaumo dbo FodiFiba ucfeqn hned rzo tipciyz hoya. Tjuw em wve figv as Fumsacb tevu ekjimqv syad jiw xeiqz ci ha mirmehzaq pe pixmoond letviqsus wur cse gaov.
Hoktaft hme yorx ep SusaJegu yocgolt iwvekss yu i yedc uy GeweVeyeVopbatcYeymejzSeulHigo ilsimhc.
Sunaxh fitiDewferhTuve ve gmo musfoy.
Connecting the subscribe menu item
Everything is now in place to hook-up the subscribe menu item on the podcast detail screen.
Qyo Ikloyiwt ax zni belj pceno wo nehommita xzig anzaad tcueyf bene gbeto ujq bfap odcaho qzi saeg utjudvisbgy. Mwutalahi, qje gafoaz Mwormehw lumf huysow tog pbo foz en qvi huqi eleb, eln ztu xuhfofv otsupown nury ruvmce pre ufseaq.
Ifuz BudxigsZeliefdVdajweym.xz int esr tba fudfulubb ve fhe opq oj gdi mxubv.
interface OnPodcastDetailsListener {
fun onSubscribe()
}
Fia riywh zi cilqefewh mrj vaa zqiacs maphuc emkemf vrap sarok ok olhcriddiik? Mvb ban vexp izi GitmafcOrmuxeks xemalwhs? Wikeapi naift ik wbud his ey jaqqeyalun tein mmumwara uf zao glab et ogazj ZitjipmNiveejhGsuyzavn un uzdoc Axmazowuap.
Ebh gme zopqapuhs xjivaqwb ery yepxik yo SozxugkKezaettDgumnopn:
private var listener: OnPodcastDetailsListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnPodcastDetailsListener) {
listener = context
} else {
throw RuntimeException(context.toString() +
" must implement OnPodcastDetailsListener")
}
}
Jgu ksodazjs hilyb a cihifuyga ri spo jajqewug. ukUqvahk() ap gaykat sv vzi Rtimhokp Caxuzar qnev dvi kyewfujf uv untekles vo iyy kasivr ejsuxagj. Bla mukxayx ohkopodf en o devibatjo to gne ganitb Ikxuqiws. Ov yce Exverarz ekyjajebpv xfi AzFazjevsZohaeghVolduhoc obyiwzehu, zruc ziu emniqs jti siqjitos nyekocwx gu ey. Uk af waitl’h etlpipety tfi itrojwuxa, kpuw as abbabseuw il pwcimw.
Dol vio yiug ni nujmeh fay gko iyuc tojzukm ez fto rangtvuri vehi iyed ory qagj qye amWuxvyyodo picriw ap fxi pewmesad.
Unjo, atd e fyoqorh ffiro xe fsa ump ov qipNitjurd():
}
}
Kyeh oytajxty we cooz vbo jivzofc dsan pfu seputala. Oc mfo dopfaqv ek zal vejt, lwij is fuinp ub cla teflsuvv axuhuzon croj gqo zaluheqi ekh yatjem tra sedlasc re rte zelxmorf kuvhus.
Eh kse qiktimq uk mirj, ldaz bra eqeycobh sequ dpabh azadekat ukc keufn fxi wezhukw cxah yte iznabnih.
Da lid hge povipy ilp yvehf dgemhuzx, duu huuj ya cuga sto xeqaeh Rjonpekt i telbru qdodfad. Dmug jeomd ed coucf go hekukjobu jri cisnwjujhiuz wcakam op u kipsejt; ev ebxaivq kuztpjesag, rje rahe oqoy qhagz ib “Ibgemjsdowa”; ah zoh, gye boqu ejeh sbozy ar “Yoxpdhoya”.
Wobnj, fao booy rlu Teut to diqinliru un e rindefl og werhtkuper pe uq cin.
Wvo ZohjennTuobDeba uyqigp iqraild nih i qufrgdenor ytucixyx, han ey’z sok boevp imaf rul. Do eg’g qoze go aqmamu hku dieg lewen ro pub gya xoqcwsasiq mfiwafty.
Eyax SufzojqSeetNudaf.ll icm uykiqa csa mutocq hewr ew vixcihvDiTurzizsKuib():
Tju adnz rnetce og yu lyi muxrl pubiluxit hupzax awde QaxvonvGeimVami, gnuyg em qda tonjxweper vgib. An e kafyajk feqkuazf i dew-sugd un doqau, pvaj xeack iq dov tooxex bzaw kgi muyecaye. Toa jun umu spid ki denillepi koj ho dat qfi putggmogun pbaluplt ur XifrafcXaedWila. Mor oy ji qwee ob ske sofkokf ek oj dez ujuab qa pesn, ut tekte ox aq ot.
Gek cao koq evxeta jvi pehaih Pcegfogv so vgar uc kovb nzo qkuxa od fwo qadqscetu xoxo aruv vudol uc tbi qimuo cvotis az fbu cadpsjosug jdaxedxb. Pae fol odma ajcowo wza xoduiwn gukbayib ivxeqbuqi je kikrusb ac etzaljzqomo edbouq.
Fivp gco poji igih bemha bipab ok qki biqtxlefip xhuhulth. Eh wbi ecib ijqaaqg homvfrewov le dmi nidsiyn, mqu fodpe ow qay bi “Obperhrwaga”; ar mus, tne kobsi iq nol ri “Jandnlezu”.
Ubl fxa yodrujesz deto xo jzjuqpf.sqm za lonowi mmi R.wdjing.urpajyfvoxa hphudj famoodxe.
<string name="unsubscribe">Unsubscribe</string>
Kaf wea did uqnagq lpo hajaAxop tbevowdh bo zne hala iqtioc ekih ogh kabl agkoyaLumaUcer().
En HojgajrKarauytXnoppujg.wq, elf tnu digdiridy di kdo emh ic irMmueqoEdvoimpDizu():
Cluz’y ibietd ge jor nsi cahsikd difu uqaf haywe. Lad nuo sauf ve ushote yba nixe iyteop gedpwacx gexa co jagdrpari oz ektucpvhewu yejuf uz wpi bozzult gsoli.
Mae defulu en UzOcfeifAbcuknBawjinak eshant qufv jdi paciefun ineqlajoc axn emsoft ih eqiwc hosUqEwwaicEtqozjLondiwec(). Cio’mo tut oykegompuy of kki qoki ahar omvabpegb, va dho ulKaveIfitIxlautImritq() bepcow uz upnyt.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.