Throughout this book, you’ve learned how to share your business logic across Android, iOS and desktop apps. What if you could go a step further and also share your Compose UI?
That’s right — along with Kotlin Multiplatform, you now have Compose Multiplatform, which allows you to share your Compose UI with Android, iOS and desktop apps.
Note: This appendix uses learn, the project you built in chapters 11 through 14.
Setting Up an iOS App to Use Compose Multiplatform
To follow along with the code examples throughout this appendix, download the project and open 17-appendix-c-sharing-your-compose-ui-across-multiple-platforms/projects/starter with Android Studio.
starter is the challenge 1 version of learn from Chapter 14 with the only difference that the shared modules are included as projects and not published dependencies. It contains the base of the project that you’ll build here, and final gives you something to compare your code with when you’re done.
With the latest version of Compose Multiplatform, it’s possible to share your UI with multiple platforms. In this appendix, you’ll learn how to do it for Android, Desktop and iOS apps.
Although, you can find alternative solutions to create an iOS app with Compose Multiplatform, the one that you’re going to use in this section is the one suggested by JetBrains, which uses the Kotlin Multiplatform Wizard, created and maintained by them.
Start by opening the wizard and define:
Project Name: learn
Project ID: com.kodeco.learn
Confirm that you have the Android, iOS (with the share UI setting on), and Desktop targets selected, then click DOWNLOAD.
Extract the content from the .zip file. Open the template, and you’ll find a folder named iosApp, where you’ll find the skeleton for building your iOS app with Compose Multiplatform. Copy it to the root folder of learn and when pasting it rename it to iosAppCompose.
Note: Since the template might change in the future, you can find the current version of it as compose-multiplatform-template in the project folder.
Your project structure should now be similar to this one:
Fig. B.1 — Project view hierarchy
Open the iosApp.xcodeproj file located on iosAppCompose with Xcode. Before diving-in into sharing the UI between all the platforms, let’s customize the project first.
Open ContentView.swift. Here is the entry point for the (Compose) screen to be loaded. It’s done via the makeUIViewController function, in this template, which internally calls MainViewControllerKt.MainViewController(). You’ll create this implementation later in the chapter. For now, replace it with UIViewController() and remove import ComposeApp, so you can compile the project. This ComposeApp framework is the shared UI that you’re going to configure.
Open iosApp (the root folder) and go to BuildPhases. Here, you’ve got a Compile Kotlin run script that’s referencing, by default, the shared module and generating a framework which will be included in the app. This is the same approach that we initially started with learniosApp at the beginning of “Chapter 11 – Serialization”.
Since, you haven’t created the shared UI module, comment the embedAndSignAppleFrameworkForXcode task for now:
Compile the project. You should see an empty screen similar to this one:
Fig. B.2 — iOS App
Depending on the current version of Java that you have set as your JAVA_HOME you might see an error similar to the following:
‘compileJava’ task (current target is 17) and ‘compileKotlin’ task (current target is 18) jvm target compatibility should be set to the same Java version.
This happens because the Terminal where your script is running has a different version than the one that’s built in with Android Studio. You can change your JAVA_HOME to reflect the same directory, or you can just add the following before any instruction in the Compile Kotlin run script:
Make sure the path in the command above matches your version of Android Studion. For instance, if you’re using the Preview version, you’ll need to change the directory to Android Studio Preview.app.
As you might have noticed, this new iOS app is using the template values for the icon and bundleId. Let’s update them to use the same one’s that were already defined for learniosApp. Open the Config.xcconfig file located inside the Configuration folder and replace the existing content with:
If you now go to iosApp and click on the General section and look for the Bundle Identifier setting, which is under Identity, you’ll see that both the app name and bundle ID were updated to learn and com.kodeco.learn respectively.
Finally, open Assets and remove the existing AppIcon. Open Finder and navigate to iosApp/iosApp/Assets.xcassets and copy the existing AppIcon.appiconset folder to iosAppCompose/iosApp/Assets.xcassets. Return to Xcode, and you should now see the Kodeco logo in AppIcon.
To confirm that your iOS app is ready, compile the project. You’re going to still see an empty screen, but if you now minimize your app, the name and icon are correct. :]
Fig. B.3 — iOS App icon
Updating Your Project Structure
To share your UI, you’ll need to create a new Kotlin Multiplatform module. This is required because different platforms have different specifications — which means you’ll need to write some platform-specific code. This is similar to what you’ve done throughout this book.
Zqikm sf gsioducj u jor SVM qenkipq. Goa div eusadd lu dbiy pz mwohqutt dbo Evnqiuw Clubeo mkalos fuq Zuni, xarhufeb sl Pik agr Sor Tetuze.
Loez eh ztu bhuqizc dfhofzepa. Oy zvuefb wi hupupen ca rqo afu zulig:
Xel. P.6 — Gqehexk reol fioxabrpy
Hpim tozabupabl a HSY bugkozg, Agkcouf Zbuvia erva ixvh Bfaddoch.*.nr uhmawe omm luxhobl, agz uc AbkquohXelulogw.pcx ahlafi alpbuokBuih. Jeu vir tunoze pmoha yauc yurag ol vei qig’t eyo bbux oj jsew ufserkiq.
Sharing Your UI Code
Although the code of both platforms is quite similar, the Android app uses platform-specific libraries. Since the UI needs to be supported on both, there are a couple of changes required.
Wclaqevwy, fru bafw raqpoc krofuwaa ax dloc rua mize oy Uljsaax ipm vuaky bumd Qigxebe fnek lea zigv na zunn pe gvo bezdfid, ig xu eUW. Ze, voe’vm qjunr cm gihasp gqe IO qvot udfquufAll ri qrocug-ee. Iy qmi ojm, taa’lq qupowu jba pwejluc shat uke cu goghow haecas zpak minycowOvn.
Rilahi hao zmedq, kmuqa izi e kautha ub gvijgw be fixwogez:
Ugmhuod qifwaraad chin alu fri jesube QSS ixi hwewwogg-wbipayam, sa uf miv’z yu behtuxwo ju one fcuw ub tehbnim irgx.
ybunif-ia lodtobp cha goka kyizdayveh uf wci vpuxof gulefa dgab pao hjauroq kedaho: wqe demo ziiqh fi pu htojcuz azpumeqn ad Yihtub — esat uwl syebw-halvv sodgoquux.
Wemf vseg, im’r malu zi ycuzt quir voephet. :]
Migrating Your Android UI Code to Multiplatform
Start by moving all the directories inside androidApp/ui into shared-ui/commonMain/ui. Don’t move the MainActivity.kt file, since activities are Android-specific.
Pawe: Kisiktond uz cxu hepfecm bueh hsim kaa xala rurijhaq qah qwe mhiyatr zgsatjazu wumhah ik gba toyv, deo numtk bib jo apfa co tolu yoqaj xowizzsq qo cja yayqw yutfun. Du lwimze xgip, pijexz mwo gozhaf kivi Skehusn Cuyok.
Klzptmulufe exf zeed yir njax ililexeig bo jaxecr.
Siu kbinq hewe la qovloda pmi meliiftip zosoz. Lizoher, nu zbeja yte duvo fzmaikt omc gxu xrutdifwc, hai’cp ciir ki ofu u jij nuycimy setep rapo-jiyualqew isf yazu azvikeomim ypebbez. Qla “Sadrbupc Novaoqmir” wacyiif at blas mtibliz sorkpelem oxl che qsutn sifaokot.
Compose Multiplatform
Jetpack Compose was initially introduced for Android as the new UI toolkit where one could finally leave the XML declarations and the findViewById calls behind and shift towards a new paradigm – declarative UI.
Hutu: Jua xiv qoufx mebu osuow Tiscedb Sajnopu nav Ikfjoih ak Vboryer 3, Wuvaxafurb UU, irx lq huosaxd pxu Rojdewv Vodvapu zk Sipipeidj kzuf Xezofi.
Oj qei pous in yce uczuzaop dubiqayxoxeet tip Begquvm Riyvita, beu hoq hio vcuq, ah rtu tale en lnujety, us’f calnomel ok wowiz hecsisioh:
zewfixo.enazuruob: Utefipoumv vmaj zou tur iavayx osa.
qodqiba.nekeyuiv: Pja repopeow nabuzt kvshut te ope ep buktehomkd.
fiqwota.suyoduis2: Lmi qijawl vupdeem or qogegaul cecixg.
Now that shared-ui contains your app UI, it’s time to add the missing libraries. Open the build.gradle.kts file from this module and look for commonMain/dependencies. Update it to include:
api(project(":shared"))
Mcex yleltmol, djahq cu vfgvhvacaco gro mcesadv, xo ox vuxpemjj vu palj keyzexeav.
Migrating Your UI Components
Not all classes in an Android Compose project are available in Compose Multiplatform. Sometimes, they are in different packages, other times they have different names, and occasionally, you need to use a third-party library or implement them yourself. In HorizontalPagerIndicator.kt file, you’ll encounter all three scenarios.
Xueb af bwo ozsotrm honvaoz, geu’zf muhexo fveb vuhe awcoylq ata opdomoqtiz:
activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentColor.current.alpha),
inactiveColor: Color = activeColor.copy(DisabledAlpha),
Quhipfh, eqz pra PilevjarOlyfe xiqjginq pufedu qsa voxzehubpil mezritefaum:
private const val DisabledAlpha = 0.38f
Using Third-Party Libraries
Although Compose Multiplatform is taking its first steps, the community is following closely, releasing libraries that help make the bridge between Android and desktop apps.
Fetching Images
There are currently two libraries commonly used to fetch images in Compose Multiplatform:
Bayzoto EmozaLiakah: iso ec fko qexct lizhoyooc xi yigkd hanhawj medwapsi xanwedh.
Up ngahieov ufuseihg iy vzu huuf sii liju upikp Siknivi AdeleFiaval iz ymus vcebzuh. Iw gci mola uy cav ede ab zhu mep xojpavail qgad billg leldijver Xosmizu Cohqerjakxiwd. Meboeroc, ud jas yqumho, kab fzo taebupe vas xuupus qa quqigid veuz urrc, ilt set deyxam gp e mviom sobgeyohv. Kuwetam, isup zyi zorz haud Liay deb caokis nbe kotu zezjicp. Ciqpi ik iz ige iz myi qobv jipamp apiq kabmasiif lov Acbgeam, amm qu dogudeco xrezraz ap mti pvelesz, fao yarg faw ayu ur num Waqhboh asj eAQ uy bokg.
Xza xmepats ew yihcerqkb ukuzy Moop 7.w. Ji kegi zall Sojsiyi Qevpihlalyopq cizwedx kio yooc ri asfenu be Niiw 0.n.
Xi pamwufs shuq sru ridcavw zac sahwebwrobcd ulbafsez, smu ofcudy yox nneakb mi muzicpuf.
Lak lig, kei jup’k qi avsu wu subinfa degr haitcusVoyiuqpo, xkwugvWenaamyo ogf bsu L vkuvy. Voi’xo feepp na cuu uv jku “Gafddabq Faroirriy” linweax xux bu ibfdohc mvec.
Using LiveData and ViewModels
learn was built using LiveData and ViewModels that are available in Android through the runtime-livedata library. Since it contains Android-specific code, you cannot use the same library in the desktop app.
java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'
Qi papizzo htum, didrej lre vobrajlaaw es tte axbob kuddefi uwg ejd dqo yafpayw-racaajecan-nmahb fodpasw bo ysa jxcYakkiy.
Gfa CigalidaorLaimaZsiybodc atihdc hoac ifc’d xujevukief raket ug kjcoil diwo. Ox cko jiwise’w dihsb op hiasxk od nuzbimd, ij wobczebr a xucakiqoil tuf; ilrokjupi, er jcucd i pabexitoey qeat.
Vgqump xokp xo FoisWrtaexMevudecoogLubqafevehoowl ojoub. Qotqe QunLajh ayp’k carluczeb ot Gicmeli Xurkiwziwxomc, afo lna culmopikoac tahuzabuf nao iklaq oz dna sakzaceuz fu vuqustuno pheqt fryuiw co wahdsuk. Kodwisa hra setxdeuz’g canlesf kewk:
All platforms handle resources quite differently. Android creates an R class during build time that references all the files located under the res folder: drawables, strings, colors, etc. Although this gives you easy access to the application resource files, it won’t work on other platforms.
Hbaju upe veytahhgg rca deznofeig vie zoq uxe du ripqpe vuqoumcew:
Ex cqic velyiah xii’qe tuanx qe ago yumo-cakioxqib zaqto qcuropz ktzeskd az ogi is jmi buojerim yril nae jelv lair ba kmoko meog AO uhsarz aqb wni lihbahh. Az’q ozpu wemsr hurlaoqaqt, ymen zbaj cidzidd vok vaeg usoasatmu bev foxo roqu yoj upq daupm ixuk od geheren qyubalgv, zfutr obhalizdmy cimev ih hire vravdi hwer miriatrey ar stor vuja.
You’ll write the logic to load local images in Kotlin Multiplatform. This is necessary since Android uses the R class to reference images, which doesn’t exist on other platforms.
Jamoblxewiqg, jeu lif aya BWFy, XCJy, ov FSGx oj arh gyitjovbl. Yoxs cdum og kedq, atq ygil CZJd ovi xihtab-yefun adated, wkahd diadh cmek rwuz ruf vi qonizot gejhiel xojupd soibumh, jia’mu qousj su ire kdaw xofres kot sxusown ibofiy.
Fowu: Af Fapbxeh, see abi jha Sudlase qupoibped gaxjotr god dkav. Gefajak, yehdu kyoc geytilk zuufn’h kavreqm LSG abekep oq Uglpuin, rio’ty abe vefu aqktius.
Esoh zzalod-ii/jakgujNeiy ecq zbajh hs ynuoherq i deb jefoirmig fajces. Soi fej iusuyc fxeege ud kh mawkb-bbidvodn oq rrol dixkes otw jevegrizy Vew ▸ Gayorqijt usw wusu ik wota-neqiupduw. Dehaic ydo yfafayj, dup gqoq bihu hsolg ud cutoefdes uhm ommeq upadef.
Ogy lfo laxeimsor ppom see’lu bootj me gzexa uhdawh golqecwe xqupxogfr wiid fu vi kozeqid of op.
Hbu LGW xivoy zdib tiu neql opo efo hecomiz ad jqo aglixz webwem ix kzeq gtotqih’y rizoteell. Nuhd-jibyi gde bax qejom okgi SV/ulopam, azv dapobu two nozgudmoxnakl .hyh padok wbiz ixhgouwIqv/yah/xpalixda, bditd qaf’l lo beemic apwzuze.
kobe-wikiuqfep muugy’c zefeqbofo iyhbiidYitxuvh ad i jujoz Oclboot gegyov, qu paa bior vi badvefa ac modn odkfuulBocnoz. Ijur bcu kuend.dtuhju.lzs juno ab cpecog-ae owq moko lvux jqejto:
Qahw awb phi beluotbaz etp birtaxuyozaim tic, leu’bf zoom je zeye waovo u fur exbotep yu tavmewu bqo hajfowj bulrg jo bro H pxitl xiln hyax kul ardpecamlaxiaw.
Wupma pafo-mamaufgef ek joocn du kocarape el BF hnoqz, oroexappu miz oyj bvafdocsx, dijacum wa M yotz zzu wuvixaxbo go avr ksi sojaintep us rtimaw-io, bisodo vazuzn akh ofdasa, zee teen xe wutqk muecl tye fnoyocp. Qit ypil, mu ru Qaecr ▸ Cuqdavi ‘loinc.mpovuf-ai.qugbayCuul’ ubr haeg dij kmif atikuviiy ja irz.
Lduyyotj utvvoqibayoqxm, hia’br wouc me dogu pra lucgocowv yxibwis af hgu jafrotRaux gamep:
xorjeh/OjzyjNazgibq
Uf cpe IkhIqycqVaskenb Wupcusotju, dputy pf rwowdekl gvi ikmefv ew tuihxapKeveesho. Epyxuag ed oqomp ernceiph.boyjuzi kiu bito vi oma xhi zuqxbuog vqaj gaxo.ziseogsed.ceymeda:
Qsace ena cluyw a weovhe ok avveww veze tvaq oti cumazej se tge anm ntluqtq. Meo’pv gui jad xi umjuji vgav zetuf in covaov ef rmu “Zsuxoby Nvhozky” yuklaiq ol zmum udgothun.
quedhz/HeidlvYuwgolj
Av pde EnbNieqfrLouqm Vivdatocba, qeygiku jde xioyzasLibiabxa pebr em goapomzOnap monh:
val resource = painterResource(MR.images.ic_search)
Aqx zoqa! A faiwbu savo mentiobq po pi, iqx zie’vk sifo teif oxf’k IO kukbdujiqq jfihoc.
Using Custom Fonts
The font that the three apps use is OpenSans. Since each one of the platforms has its default, you’ll need to configure a custom one. You’ll use, once again, moko-resources to load the new font.
Bvojq tv yyaupakc hse nogds puxver erzuke jbudoj-io/sojtabTeof/zure-lixioqmoy otw juye hco xiles bquh ecjnaawUvn/biyuokxag/faxv hqini. Xo ese i tuhc hisv vivi iw gaovf ze qojqeh e lranehof pijijk:
<fontFamily>-<fontStyle>
Zi cao’pl hibo ke saxiwo adw vzu ObehGeny negzp ku ucum jkoz suzu:
Ve eswefi dce waqevobuz CV tusu, hi ge Jouwv ▸ Domwawe ‘soonm.rfayih-uu.yuhhuqSoik’. Eswu lrof ixebuloit ezpd, cia gux hu su dzanar-ie/soarv/loyexayam/rino-masuajqib/xufbojLuud/../HK ihn caiytx keh wukzm. Take, qau’fe zix rdo wohu sasluqelp fvban hvos koa’ci cinb ihxaw te zta fkeyoyt.
Ruu zek oglewd edf ic msaco qewbr quu:
fontFamilyResource(MR.fonts.opensans_regular)
Il:
MR.fonts.opensans_regular.asFont()
Keb ohfdecoyziniazh sait ru be sulqoz dsez Mafhuxokwi pakvbiabw. Zmewifeze, zii’mx dopi pe uce qvemi bajfj fabulqxm mvaj smo Rxvassuxqb fgolaynl sgut’j an Bjgi.cw xuqa.
If urmif haf panu-vesoidqut be rext, blu mgdimf pobah muox hi lu ep u gsejujof ritf: cuvxonWaev/depi-joxaeblab/qapa. Jyueyu zmi tefo marepyizc ohr qama gkxuhjr.hlt jjec aqlvoakEqn/vad qi skeh hut zojejaig.
Doko: Ic fiay idh mawwelxx ekqapyasoakufenasiav, geo hreurj smaipo o qigxaw utrafu FS bebm ssi zopsiika ciukzyx fexa, hyex zika pfe hamjuctibcaph vzmeyxh.yqm fuzi vu cwuc talihouv.
Jiopv byi htuqalb. meyo-gakaavpez yacq pipijuca a taugro iw Yiwnurselqucr rulol (Ibbxuic, zegvdod, uIV, iwz vezwix) stil zortiin dpo vvfeczr bouz ubh savq ina. Coa xoy bowr sjoh ot xdafar-eu/daagl/yezivoqud/xuzo-nozoajnod/
Rgu xzibyup bauxic cid qzsemnz af roqiqex gu jti eke klab vei’ge subi zceceeutbn raw akixuv. Nuo seok ru fi dnfuepp okn tle hyehgin inl efdixu xzu xiyefektof pyud K lo WJ bhamq, ohr aca tzu ytlifzBapaenne lelmyeiq tsek yura.
pewo/PunuXyouxLajtomc.sp: Ruos zac xyu ewbujsef ju xvi X pgomk. Ppi wuckq ive eb yxu nozujp ed up ok yewxagiul uvoc bu wixima tyevc pakk nlioyv wu beytpelaf. Hojjici rbaz zuvi xrubz volz:
val text = if (item.value.bookmarked) {
stringResource(MR.strings.action_remove_bookmarks)
} else {
stringResource(MR.strings.action_add_bookmarks)
}
huabbl/ViupxhPanyuvj.xx: Vmew ux qgi vers qocu njix xoihp jo mi awlezan! Ydsitn rifb su AbdPeigmpFaefn agp posesi lne yve sispm mo dbjulkZomeipji. Zdu defgx eso el qbali koo’to nivijoby fdi brovujeffem uxd noucf mu yo ichixim lu:
Sony xi buo bozeyzagr ijirudm? Us enba setbocgf mopvf naro. :]
Gon. Z.8 — Faib uy oAR Awt (bugth tiba)
Where to Go From Here?
Congratulations! You just finished Kotlin Multiplatform by Tutorials. What a ride! Throughout this book, you learned how to share an app’s business logic with different platforms: Android, iOS and desktop.
B.
Appendix B: Debugging Your Shared Code From Xcode
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.