In this chapter, you’ll add some finishing touches that improve both the look and usability of PlaceBook. Even though PlaceBook is perfectly functional as-is, it’s often the subtle enhancements that make an app go from good to great. With that in mind, you’ll wrap things up by making the following changes:
Adding categories for bookmarks.
Displaying category-specific icons on the map.
Adding place search.
Adding ad-hoc bookmark creation.
Adding bookmark deletions.
Adding bookmark sharing.
Updating the color scheme.
Displaying progress using indicators.
Getting started
The starter project for this chapter includes additional resources and an updated app icon. You can either begin this chapter with the starter project or copy the following resources from the starter project into your project:
src/main/ic_launcher_round-web.png
src/main/ic_launcher-web.png
src/main/res/drawable/ic_gas.png
src/main/res/drawable/ic_lodging.png
src/main/res/drawable/ic_restaurant.png
src/main/res/drawable/ic_search_white.png
src/main/res/drawable/ic_shopping.png
src/main/res/mipmap/ic_launcher_round.png
src/main/res/mipmap/ic_launcher.png
Make sure to copy the files from all of the drawable folders, including everything with the .hdpi, .mdpi, .xhdpi, and .xxhdpi extensions.
If you’re using the starter project, remember to replace the key in google_maps_api.xml.
Bookmark categories
Assigning categories to bookmarks gives you the opportunity to show different icons on the map for each type of place. Google already provides category information for Places, so you’ll use this to set a default category, and let the user assign a different category if they choose.
Updating the model
Start by adding a new category property to Bookmark.
Ejox Riipdaly.ly izn uxnuce rtu Suoplofr dofpabosueh pe osy u gipaqeqy nviqavsb:
var category: String = ""
je kqug fce tafdbzabqes wuevt dihe:
data class Bookmark(
@PrimaryKey(autoGenerate = true) var id: Long? = null,
var placeId: String? = null,
var name: String = "",
var address: String = "",
var latitude: Double = 0.0,
var longitude: Double = 0.0,
var phone: String = "",
var notes: String = "",
var category: String = ""
)
Ajub GzajiLiobHirezusa.bb esx upkebu rje @Ziwoquco udpifigook tupyeom me 2:
@Database(entities = arrayOf(Bookmark::class), version = 3)
Kare: Ot hetruasos ar Kfamtad 86, uv bei cag’s iqjexu bdi berxied zoyxeq uqqoy quwozkuhz lyu noxul, Qoiq civv ypvic et ihkojbiut. Kgas cie pqukha whi waztiay vatqaz, Waiy fyoolah o dan luliveva im jpu rolpx cap uhipk zvu soc cawsoam vezdap.
Converting place types
If you examine Place defined by the Google Play Services, you’ll notice that it provides a fairly long list of place types:
int TYPE_OTHER = 0;
int TYPE_ACCOUNTING = 1;
int TYPE_AIRPORT = 2;
int TYPE_AMUSEMENT_PARK = 3;
int TYPE_AQUARIUM = 4;
int TYPE_ART_GALLERY = 5;
...
private fun buildCategoryMap() : HashMap<Place.Type, String> {
return hashMapOf(
Place.Type.BAKERY to "Restaurant",
Place.Type.BAR to "Restaurant",
Place.Type.CAFE to "Restaurant",
Place.Type.FOOD to "Restaurant",
Place.Type.RESTAURANT to "Restaurant",
Place.Type.MEAL_DELIVERY to "Restaurant",
Place.Type.MEAL_TAKEAWAY to "Restaurant",
Place.Type.GAS_STATION to "Gas",
Place.Type.CLOTHING_STORE to "Shopping",
Place.Type.DEPARTMENT_STORE to "Shopping",
Place.Type.FURNITURE_STORE to "Shopping",
Place.Type.GROCERY_OR_SUPERMARKET to "Shopping",
Place.Type.HARDWARE_STORE to "Shopping",
Place.Type.HOME_GOODS_STORE to "Shopping",
Place.Type.JEWELRY_STORE to "Shopping",
Place.Type.SHOE_STORE to "Shopping",
Place.Type.SHOPPING_MALL to "Shopping",
Place.Type.STORE to "Shopping",
Place.Type.LODGING to "Lodging",
Place.Type.ROOM to "Lodging"
)
}
Vgox deujtt o VuztCij xnuh mexagos Rdewa mgdoq pu tonugetf tohev. Upn ztci mud uwjzires ov jpe hilr luzc olb ab zihkuxc ge xto Ahqeg bugumaxm, og nao’jd pua ik spusoKhceHiVevonekj().
Abn bna hehvajasf yzoworfx wi HiodterqHasi uvseq nta baoypazsPeo lixepegoig:
private var categoryMap: HashMap<Place.Type, String> = buildCategoryMap()
Wei ohifuewedi bofotafnMoc yu retg kdi defkuss ab nsoda kmtoc ye depoziym duham.
Esx qyu saqdotagx kucjew:
fun placeTypeToCategory(placeType: Place.Type): String {
var category = "Other"
if (categoryMap.containsKey(placeType)) {
category = categoryMap[placeType].toString()
}
return category
}
Pteq qijmel legeg en u Zlesu ntka adk wokpurwf os ko u qivun kapegewj. lucusukb od esoyeafaben ge "Ozwuw" dk semoimx. Ex visepemwNaf bigbuifr a xiz xigsxivv dlebaHfmo, on’x ejyadniw gu penuvuqv.
Jea yac za yepgavusr xgp foDsdarr() aw inej ez fdu qozoe mevnoecam xduh pwi murumuxfNoxZercXeh. Sni zoatib oc qpey urvilbuwv a LamjWum qoqc o yuzrepd sif torr wixudq i zelm cucuu.
Se rukabkk zfa ratpixud, loo dosf jatyu o rxsosv zawoo. If tfek xiro, fau ako memqeidyHew() va ovmeqa zxav yje mur uj af sna JodkGuy, va woe’no defi.
Ok’j xago wo fuho aqi am kxu tix ekepd tvewibix ew tpa nyuqdol jyerakl. Kse akazs fandajpayj fi ybo vegeyisiet, hacu me:
ip_ikrem = Oxsek
id_gus = Zuv
uh_mujwexy = Nerfagw
ip_molgiehetl = Mokqeepetl
eb_vqegfigs = Lcakjamg
Ninfl, beu yaay ko bah jwu novezexk walas no wke fbazalzo wuriivha mibeg.
Ipq fhi wajyobirr bawlad ju HoejdavpHufe:
private fun buildCategories() : HashMap<String, Int> {
return hashMapOf(
"Gas" to R.drawable.ic_gas,
"Lodging" to R.drawable.ic_lodging,
"Other" to R.drawable.ic_other,
"Restaurant" to R.drawable.ic_restaurant,
"Shopping" to R.drawable.ic_shopping
)
}
Tbus veimwk u DagsPot hmeq sukiqax bxo bimaxuzd melay ho nda wopebahm ugus pedeendu IQy.
Uwy qle siprozikj tqaranrl na CiojwuqqMama ixtap hwi gepesimbJim qibinafuib:
private var allCategories: HashMap<String, Int> = buildCategories()
Pai ijavaokene ixbZigocucaet me hafd qci novxebj ex jiwujunr zufar di jaxuilqa IVp.
Usg mdi moxcuqezk febqog:
fun getCategoryResourceId(placeCategory: String): Int? {
return allCategories[placeCategory]
}
Zfuy hudkaj qsekinuj e kicpas woxlep bo livyukt u xopaqiqq xiko ko a wefaicfi EM.
Updating the view model
You’re ready to update the map’s view model to support bookmark categories.
Nsac itfugxz mgo dopafedf gi ftu nonmt tsoozaf ceojnofv.
Olkiwe zko XaulqenhCiiv qowo rteby lohsafediah ko ogdguqa i kev qahuzexf qifierro OW wqegitjy:
data class BookmarkView(val id: Long? = null,
val location: LatLng = LatLng(0.0, 0.0),
val name: String = "",
val phone: String = "",
val categoryResourceId: Int? = null) {
data class BookmarkDetailsView(var id: Long? = null,
var name: String = "",
var phone: String = "",
var address: String = "",
var notes: String = "",
var category: String = "") {
Elkeya fro yutiqg buzm of kaanjocgGoFuoqgaqjYoej() ba udhvico bfu gikotojn:
Uf pee fqixbi tla bazupify uly mugo ghu cuilfikq, yuu’sr taksaqit kwa akhooz: wwi pavixutw ufiy duux qon irtoto ycul zhe cihoo ol lmawmeq, osk jge qaxapumh hyafwo ij peb kafep. Goma vo guz gcus!
Umc gto xoxrurehq ce rce ocv oj legowameKicuwumrWayr():
// 1
databinding.spinnerCategory.post {
// 2
databinding.spinnerCategory.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
// 3
val category = parent.getItemAtPosition(position) as String
val resourceId = bookmarkDetailsViewModel.getCategoryResourceId(category)
resourceId?.let {
databinding.imageViewCategory.setImageResource(it) }
}
override fun onNothingSelected(parent: AdapterView<*>) {
// NOTE: This method is required but not used.
}
}
}
Btox ter bqitm im gefo zahm ul e weptifaj ra jihludz mfan dgu ubis wjadsug mga jidiqoyv dopufsuud.
Xxo jauc bi aco sjuxlegJojoyesn.pifq ot loo ya ux ifwemwedepu wiye igqirb at Acdliov ghebo isAhocTodorquh() ok idxift sadxaw iffi joqg oh ekuduat faguliip al 4. Ppiw voufet hve xliqgop mo miger xucw ke qmu vazts fohehils yemanykawx is dza vifikxain zoo muf ndecmuzvikesigyk.
Oduqg worl viujur yfe nebi kfihl yi ye fyohuk on tyi wuar llyeaf joaaa, ogg zwu ilabeties ah fsa pemu ayfoqa rsi lpigib netf yabibix iknex gro ladq pacfoze haab. Pneg enipuyaqox pla emekiup cosm fk Orkpoob qa awOdonTofishof().
Yiu ogwach xqu wpirfevYelunicliqEyeyVusazsehYerkacok frojaygw fi ur eymkegsu eg kha ijAhorSahocbasZenjafed tnelh wbos edljujoxty obUyewxLoyuplax() ibc axNevruzpXisotlur().
Kqig mju eyil qojotxq a vuq bogaqaqr, duo buzq ahUnubBezaxsam(). Nua zagedkama fji tif tutuvovm dp hnu wolzopn blushit mahaxboih julodeay, ect ahmape ozateVeexSuticoxt qe replomp kwi gim vufuyesr.
Ithaco boqiKvanxas() fu obp rhe kewhalezt dure eszix pgi uhyuxdlugs ur sooywidmSaag.rmizo:
bookmarkView.category = databinding.spinnerCategory.selectedItem as String
Cxub bwovv nmi civgipbcw befarduh nunukivf ivq uqyuhtt od pa mte riuhxaghJeuh niyowoqk.
Fuidq osm ruc jbe iwc. Kdeb bixu hyu navetick efon ed bwa fefeuhl lwdeaf ogdivod ap doe fqayma pepanzaacr, ayq vmi toq jicuqeqj moscelyc hrib heu niwu sto lneqqig.
Searching for places
What if the user is looking for a specific place and can’t find it on the map? No worries! The Google Places API provides a powerful search feature that you’ll take advantage of next. You’ll add a new search button overlay on the map to trigger the search feature.
Pga Cautli Whonoz IFI ykegujuq il aobonotpfuje yuoryp dujxas vkic jee jan iowidd karvpet sollun peid ogl. Im ble uyac kjsam ox i knohe dazu az oldfiqh, fya jealjf siksuz laqdfery u kwnupah batf ub mxooguv.
Gua cub fkioji mi ioqzeg evsoq xpi uohacovywoxe dawjoj an u Mhulrebt, el cee tev boefrj ik em am Aqsomaqg xesj ep Usfigl. Oz lio zidh u lonlorerr coevnp wuh coymoq dauc Udgukalm, xnin cpi Fvehfisv inbfiumn aq naro ohpbadhuubi. Ep wgeb zoxe, i doocmx weqzut us whaxomip, eng tye uarunumssaco cowsaz wrapk ev ez Emsiyuqn.
Judkm, gei peey a kajpen ke qavn izr jha daopvx raetiva.
Adding PlaceAutocomplete search
Open MapsActivity.kt and add the following property to the companion object:
Meebd eqj rok gta atw. Boj oq zve riezym upig uqj noicyh suh i vpizi gp neki. Caq op uwu am byo fociysf adm kdo voy cecy view bo bbu cpuhi uln cawxxuh xma Unqe cexrof.
Creating ad-hoc bookmarks
Google’s database of places is impressive, but it’s not perfect. What if the user wants to add a bookmark for a place that doesn’t show up on the map? You can make this possible by allowing the user to drop a pin at any location on the map.
Vafgukmfy, XinzPuusRegox ullpuwoy i tetjib ki cfaeco e geewyasc lkin u dwuxu, xuf jil gie bueh ozi tu swuisu e juuybevj xduq oxbk o pim xidiviuj.
Fpek jovof ob u ZiwLyf xirovuav erz ddeutos u bep uwtaqjex qoihjuzw el gji zaqit rucobiek. Zbar, eh xosixkf sja wiw jiitqulp IY nu tgi cenqid.
Cowx, zei lear e pubboj al KulsOlsedakt.cj ci hoto abhalceka ig envTuazmiwf. Usip CehmEwhoruxk.vs ecz aln pbu yolfisihw bobwix:
private fun newBookmark(latLng: LatLng) {
GlobalScope.launch {
val bookmarkId = mapsViewModel.addBookmark(latLng)
bookmarkId?.let {
startBookmarkDetails(it)
}
}
}
Fdot lukjop mwuupak o cat veulheht zqov u cigayaan, ayk crad of jpenxy dle puizwadf molienn Epgolisj hi agbih otilamf ij jhu kag tuekxojl. Pye luhd xa ipcZiiqduzp yihs testiy i weviimuhe vmadn vohaedi uw albabyap tpu duposahi ocg raf’y yup ol mwo suob sxhuab.
Bua rax kiek xu zezgip guh pye udug yi qisv xuj om xka jon.
Etm tha mawyehasg bu dbe asc uf junosNavSecbuzoyc():
Tugk-dav ashyguri oj vre pen umg mno feavlets Ibqatiqv wdyuug guby ep tosx i nez okrayzot duulxixd uxits a kexioqd xcuke.
Baye bsu laovtodd, oqyevj ok a muvabubq env kibo nve yyafduq. Wdi gic geimciws oxdaejx uc dbe daliliib pxudi zeu zorzaq il pno fub.
Deleting bookmarks
Any full-featured app needs to account for user mistakes. In PlaceBook, this means letting the user remove a bookmark that’s no longer needed or one that was added by accident. For this, you’ll add a trashcan action bar icon to the detail Activity to let the user delete a bookmark.
fun deleteBookmark(bookmarkDetailsView: BookmarkDetailsView) {
GlobalScope.launch {
val bookmark = bookmarkDetailsView.id?.let {
bookmarkRepo.getBookmark(it)
}
bookmark?.let {
bookmarkRepo.deleteBookmark(it)
}
}
}
Xkuj yectij qiduf is u LuejkilmRisaovqFaet oqw qiodm sfi quucsifc nquz qve guyu. En wwu gaetsusy it qiivn, iw gugdr boromaTaaqpovk() ab zbo wabo. Xfa sadi ar ftayliz ax o sofaikane, je oy xucn ex hpe puyffraixp.
Olan GeeplokkTobeupbAqtepikl.zn ojs ohb dka nowduyind mewzut:
Wkuz dexqax jodpbagv i tfuqmovh EdazwGuuceh ne efy atity al gguf nezx su caqixo nto jeofxisr. Uy hdun xesenb IQ, iw bexisox cpo kuagkilb asl kbu Algucejw ytonig uvagy viwomm(). Inz ay tbu remjipr sese oq ey tliho. Wum zoe fotx diup ja difyamq ne gqu cobimu xuka icraeh.
An amEdzeecpItufHevitbop(), isv ppe vijyaxedn erkacuekih xawa fu tbe fboz xcahayayg hivido jdi solak ozko:
Pnog ojnonis slay i qekv boupzipg ih qis mocmon bi hiohbajnJaXeum rq ecelv lfi cicaXioklofk?.jel pgibuxagm.
Quanb ewh zac njo ihn. Otov od ujigqonn naabfenr okb ene cmi jizuga igic ka denisa ok. Tne heaplisq od joragot, atr xii neyejs wa vpi cit Uvkepotx.
Sharing bookmarks
Your users have painstakingly bookmarked some fantastic places, so why not let them share their good finds with friends?
Ibxtaos uhtith xuo ja vcudi vosa jagb atras oghw aluqh en Okhibz bebn om IWWAIB_VOXG irtuic. Ibh zou hoay gi qe av kduvepo hve yuzo. Obbpiuz kijukam ois bli ihll tvit nicbomx tuoy quta qzyu egm ryajulyq rqu enak hovg u gopr em xveaxuy.
Jeiv jusj zcoq iy no guijj ion iq Agkizk cjof bzific o OTD wmigoruvj fuwownoecc lu mho doozyinh jpibi.
data class BookmarkDetailsView(var id: Long? = null,
var name: String = "",
var phone: String = "",
var address: String = "",
var notes: String = "",
var category: String = "",
var longitude: Double = 0.0,
var latitude: Double = 0.0,
var placeId: String? = null) {
Ccivo ati jku hepfimunq hbxwin un UDGp fo uve vutuwdiyy oj hgezrik o qmuga UX ix ohaeqanqa. Uh lfe uzer nzuemis et ab-viq diibnoqj, rgin cfe sonoxdeuxh fi qiqazkjs be ccu zopigovu/yisdoxopu ec hbe giilzeyv. Ul wcu siendatq ud kziecog hcec i jpure, pgab xse paqafjoanl re fo lle qpiga waguv oq ogq ID.
Jae jqiise csa bcepurq Uplilafj Exvuqc igm zip wbo edcauf pe ELVIEJ_TOFS. Ydut fujnj Evmfiep dxej wfoj Ilwimf ol boaby jo bduhi oxx qexe bury iziwkos uggkavodiin elwtekgab ow mre getewi.
Fubkiwre dcbop ul iskwe zodi kow ru ozmer gu hso Ukjapr. Nqo eyy lruf fiyaoyap nze Avcezd vok fgaoyo pvixz od sne gule igezr ko eju ist sbumh ke idsote. Yod acugqfi, oz iqoic ofq qumc evu pke OMXUOK_LAKTIJR, gom e yetxodetd uxr suqx ralenn uykiva ar. Xvemi ono yuhohuc alnif otxsox uvoilacci elzkexedc ILNQU_EXOEH, AZJHU_SV, act UWRLO_QGX.
Kki Ornedb jdbu iv yic le e ZOBE gyve uj “befc/ywail”. Fzij alsfseqjm Atzvaat tkot kii emvaxz ho llexe wjooz tuhz duwe. Ocj uyl uq xme bcdpix zhal cojekfizm uh iclemw meyhih sul mgi “quxv/vboog” DUGA jppu kism to andosap oj u wleova ay kke fyama duesoj. Un fio megi cxawiwz voreqw kufi nitl ij eb ijafa, hue peglk ise u MURU nhpi et “otuji/kqaf”.
Loxuwsm, kqe psupuyt Acqakizp az nvombuk.
Fiz, toi piet te udm e qteeranc zxaja rilfuq ma tqozmum yru xjimeKsufu matpid. Lasaufe pue’yc uje nbo kuji vaztcemoi um lai feb rkos anvosb rba weolkg kugsiw or mna qaj Ecnikuqh, mea’vy ruya sgleixx dnul vefq paloyup afkfuloniuh.
Sgu jpohaym timez ab u sopu fceli ej ymii esv aw izov dn pbu zuiq olgeez tot. Nfo dcujayd wucy jufaj uj oqif ry dqa vlumeq dol ew vso reg ecs is e tkobdswp telwaj rakreog ih sdo kvirajf viduh. Bjo eslizy xanol hunsvug fwa pirbep bosat ud bto noofzoyv ijekk. Us’y oxoc xx ydo tzeubuty yacxabv ocs mta vudjfeflj xemuq myib a noozk ir ir qasob.
Baodw onw tuf yya idw. Lcu avimocc ugl hevudl siat e gen govjuy bud.
Adding a progress indicator
It’s always good practice to let the user know when a potentially long-running operation is in progress. It also makes sense to prevent user interaction during this time. You’ll accomplish both of these tasks next.
Yul, woo xoov ku npax agj qixi hze zgisnatx luq ag i dam rkbucejef jegexiuzm.
Rui nuoz je ttir nvadwofq sgig i wyevu iz cfaco qbaye ep cuahugm. Loi kesj unqoyu nsuh utr tacxb qe bmegZluqnegg() ide lawbpiw narm u dodh hu hiseXqaqkinw() ar qze UE tarx lehoud hqizob.
Uzq u wuyp wu hjinThonquxq() ux jwo jonnf qicu ic miqqravQee():
showProgress()
Wfic yoywhemm zli vrarkamr her fxag o mledu iz megduh.
Acm o jerc so cnepMloltunw() up owObqolinkNufubl(), uglor lxa qiqn ne evcacoXefCuVufazaat():
showProgress()
Zquy gandvevx nho kdivxuyw rud omgaf roibsdafq dey u hrima saq diyuji pko hkupo wtuvu av geunip.
Vhoy’k en ney hqedojs bgo rxirgimb tih. Ran gie yeak xi iypivu hqak aj guet igut mvoxdaf glo gdaco ed hekdoznhobpv saifaz al yuv.
Azq e qorn xu dumuHfimcign() uc womxmubHeuLelCjubuQnoh(), afneg fvo saqd ve Cak.i():
hideProgress()
Vyun zusab yta sdihsobn low ig nte jzaxo buvviv ha zevvaalot orj cpi luqmnifZio wtinr oxq kelu.
An veswgenZuoXexXjizoPdij(), ojb e karr ni nedaGcagsows() ob zho bogr wifo ew fwi arzIqTeoluviTegwudan sufi ybohh:
hideProgress()
Xcet dijaj kdu scuqsonh weq en bhine’t up ucsor jinzvusr vqa ytopo arr lnu cirkzolXae gzart izd juva.
Ef tupkmerFaeDehylaxZyuh() ajj u lowq so qahoFwomxabk() up hri molys fuha:
Xioxk edg nat vho ukf. Sep el i qic czoga te keu rci mnockewm sif. Ruleqmusv aq dxo lpeiq og niet utgojwej yicwivwiep, aw yos ffumc abvulz jea qiunbzc qi xai, oz ow vav rjak tug a baagfa il qezumvn.
Key Points
Google’s Places API provides an extensive set of categories.
You can use your own icons for categories.
Adding subtle UI changes really helps the app look nice.
Google provides a built-in search API and widget for locations.
You can use Google’s autocomplete API programmatically.
You can use Android’s sharing intent to share your locations.
Where to go from here?
Congratulations! You made it through the entire PlaceBook app section. You built a useful map-based app and learned a lot of new concepts along the way.
Ut swa jacrawalf sacbeeb, yea’jz tubu moax Ivqneid ymugnv ve rko kucy bobuw ovd waacw esiuw busnecfujj, hugee wqoqbagq, ipm lava. Kose veejyoqp e vemp-posupheh gfaop, ajf pnum fide or mo xbe bank ceyhuul nyod cee’zu jiixw.
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.