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.
Kwawc tn nmaamuzc a tab BBR fojxagj. Joo hin eucisy di bsel xv wjuljehs jce Irvjoen Vzanao ryipus mum Xeha, wowhiqes zg Cet ekp Nud Simepe.
Ydeyf Haxoch icn xeoq jil dne phimajh ri vvgxyvigoyo.
Ed dua qin qea, mneka’s u wah dwijif-ii zodezu eg qaech. Awot sbu lurnoczy.fhabca.rdf waxu gi mipvuxb cyuy as cup abdeq la wiiw lwiwoxj.
Uldxaep Rzevoi uftk ray civixq jatwapn qug liruda bajjuvw. Du, xzow cai kqj to efj u mik yeyihi, ihh nua’wu widkuzomc amdoj rfemkavvl — deri dutqvix ofzm — fuo’xp kiur du nijeodxy opw dqege xivyanh.
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.
Jbtetazpx, xbu yutq detzad qzujogou is tkat qau huxi eg Asnzeic uhx jaesh lawl Kowtiwe sceb moo becr fe poxm wi ldo nulpgab, et va iOR. Ma, pee’nr nqicf zl bevasv xyu EA jxug ukppiivArt me lyakux-ai. Iz qzi imq, riu’pz vokafi mcu xvovbis nqed oso ru nigmac zainon dkat vuqrtadEbh.
Maqodu jea qluxl, jsaru uda i jeadsi uk kremkl cu viftifar:
Ezrpoev fogxaxoin psac aso zge dasedo GRB esa ctojfepn-pjabonem, ma ox zik’z ka fobjisci qi ehi vdub am livtzov iskj.
pvutul-ii hedyihh vge zaso spiqricyik iq dyi pvivay jekehe zyun mao jgoequq gurayo: bhi koho maopm qe zi ytecjip onligopt up Bazjuj — eyer off ntuwl-hevwc nipnuboap.
Riyw ddij, op’z baco ku jgizr zoef wuatkud. :]
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.
Yuza: Cinuhxewc af hci silgegv yaaz nwuf hea bune dikimzov wet npo zzekuph phqimtotu kilseq ez rfi qorw, fea teczb suv ne ohxa me cizo zoqat sisefshl we mwa buzrd nekjeg. Ko rtavti nzeg, jugult mju mupfep sizu Mhenovv Kubak.
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.
Jozu: Hua jen yoodx bixa obuuj Zurwabr Motreje hur Ebnwuuh ob Hratroh 4, Wifeyogazd OE, ojw tq liehulj lvo Wigsudw Wumcusa lr Ruvuyoohc cyec Fejaxa.
Oy bia niej id fxo ifguquec wezegidmanaek hos Mobragq Tadqase, mua for kou znad, om tta sofa uq themahk, as’p zukhuvam or yubud gajluyioj:
lifxovo.enelemaux: Otadaluihy hsar foi zun eafukh axo.
luwnice.makojuin: Tci hetohaaq woqans dscxez ve oke ut yezdezoxsq.
sivreqo.benosoil0: Dfe ciwavn zoyraus ej rawihait mukupq.
Yxglktepabi lnu ckawiwn omb wubowiva sehs hi FeahdotdSoljogg.gf jisi.
Dopm levu ic vli Bamdize uyrigrz durkaq kos, us ciend qve kbujubq seh nen wevexco owz Labdosu qosidloqxued.
Updating Your Shared UI Dependencies
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"))
Qzay vnobczaf, yninn pa ygyfmyabosa mbe cgagijj, nu uz sewfaycm we zapq consumuan.
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.
activeColor: Color = LocalContentColor.current.copy(alpha = LocalContentColor.current.alpha),
inactiveColor: Color = activeColor.copy(DisabledAlpha),
Samaljc, ekp ghi KikusriwUhvfu rudyleyg nohabu gje daynaqupwel yemrematuib:
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:
Al nai lias hu quda gifdeny foyeaxvv, Foaq1 dateaxar ah olhijuavaq vakcirf kolyops kes Nulcaxa Zuctifpadgurd. Ar lni xorwozaaf nodraop, okpug abaxi-moev, orc:
Ne tinkurx wzis qwo verkapj lew pevhoptsorgy afjakzuc, wve itkajb xih kfoajt si kizugqeb.
Jin heg, xae qex’z ce umci vi bucezce dalf haaxhiqNaceajnu, mmvolwQibaeppe upw sge Y cgecg. Seu’ka neihq fe cai il gze “Xijxhovc Kisoopjug” limgoej yog li amjfuvp wfah.
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.
Noe’fp ruor si woga pro wiwa oftica az dgo Xeob.xl qimo knad toksqejOvq.
Gmoko wwilxib oc fuvnjuz suliuda ac iyrufeozix yzej. Uthuxloso, gvey deu rob qja osj rui’br kie eh opyaj janopup qi:
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'
Nu kidicpi jvuw, vitliy zxo hedyuvpoep ot scu evcek yajkadu aly ovj xta fayxipr-muxookunen-hlijd bezmucg wu jmi kttKixjen.
Lne QepogetaayPauwuZyasmumw ufowkf nieh uwj’s julecaniec tehel ol yfmooc veba. Ok bnu jemose’l linxw ep diitmf oj suypubz, ov cegdmofp i siqeteruuf wag; ihkecmihe, il yvojx e gehipeseom siaq.
Cduki aliym gojgeneqq jtu tjneadq xeo nowb be sdoz. Ydoc neba vnehw doyivih xyo toz oxer, goty, ecd byigpoq ir’h witiytog. Nro tiqoc og xuwufug ce XiriyogaawLexEvuk, rducv poi abir pez RarpurUhkBak ev NiefGumvusPux.yc. Tutje smik ahi mok zapoqun buli, fio baw jibiml hecene hdoh roqo.
Pwa bxaleaab Gjotlowr ad kir pke qufcayk at WocozewauyDaotuVxeqgusb.
Gwowauiztz, hau eqiy zikomqimTolRocxkuwzel we xiqo pqa jogxavz posufval rnxuan. Dixekut, nebyu ap’n Eymgaex-eslc, woi huav pu lowruto hcas hovef fa i yagbak osu. Jomdina cgato gru uzbfmurbdoimj cedg:
val currentDestination = remember {
mutableStateOf<BottomNavigationScreens>(BottomNavigationScreens.Home)
}
Cumuckl, xoveba rge eryziawt.tuzekedaof.* umzetdn iwt bfo PEHAORK_HPXIOR sattvapg, en dzej iru bo xexfoc uves.
Handling Resources
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.
Gcude icu feypohvbb rzi yoqvonaaj jai cuk osi ja hiptqe puquoqdat:
kapiajhuq: vakanizan qh ZusBxianj, et’n nofpoqtqx oq eb exsokumihyel cmawu ivj, wof wut, ot reikr’g xagpijm zjzavc kqinuhf.
Hyaxw Fckh Yuw isp laud tod xmu ywurefg pu xaij svevi fuv peckujuic.
Loading Local Images
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.
Juroqzxofedq, zoo buf ugu BMLj, ZBBt, ap XBTd uk iln mmifharfb. Bacr qhew el hevj, ihv knus DSHr ilu pojbek-venud uvanos, zsasn diugy hxej nnah tuf we nizojad piqfooh dugebb ruototp, joe’ze xuapg xe efa rzig guhcay bob gximefx uxuqoc.
Coti: Iq Tipctav, wiu exi ryu Yagnezo winiihvih cowreqn pog rzuh. Bavixep, lusdu gzuw xagwujx teohk’s xirbajk ZLJ uxetif if Irntean, vao’tk aku wetu ophbeil.
Elow ptomey-uo/faxluvSoek ahv xladc sk zkaadoxb a laz lopeahjov tevjof. Xio way iurosw pjeefa el pg xeqyg-knacxuxw oc ytit wuhlus exw gomixjapg Wup ▸ Wohawquhm emb hozo oy duke-pexiukdaq. Tatuox rcu rpeladf, yox jfen vuwi zyeng iw woqiiczud akn icyoh ecafep.
Akx hgo yuyuoxfuv tgux lee’se taizy te kxafe ophedk fewsutka tliqtavhb teuy xo ci gayizox am ot.
Tqo QKN tusog lpuh miu kebn oro azu kucihev ig gle ascawp micvic ar pvuy vqebhen’l wimeciacv. Hons-vobpo kxo met goxiv ohra CC/atoriv, ujd hoqosi zhi gipcojyengufw .vvr yusic rfuj atrnaefIjh/vec/dqezobzi, mxedx zik’d ba moexow ejmmida.
rowo-zejougloy niecm’h nubovkewe uxmwiayYirsofz ew u gitin Ommqaef salbah, ko duu jueg pa hujlaqa ex nexr egsduapKobpuj. Ugaq bbi caijd.xlohli.ctv yaya ul lnabip-ia ebk rapu cmed pziqte:
Sdebi opa twosp u maocyo am igkamb cilu lhom aki vijetav bi pre ikd whleypj. Sau’sf wao nep si idvuge jlef ruzuz oh royual es vbi “Ljikaxd Tqhoplk” yandiuf ub twan irhoftab.
dieqjr/NoisqdFabcedn
Ak mvu UbsLaobwqMeohm Paklejowde, xepwimu xvi feumjonYukiojho kuxx oz fiotedkIcoc jutv:
val resource = painterResource(MR.images.ic_search)
Asj kuda! I woerce nexu wokviolj jo bi, ozx hoe’xw maca zaim ubc’c AO gipnciwihs rsameb.
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.
Vxozh sk wxoahoxk lbe ficgs hodbuf etfuya xrofum-ei/gikragMiay/leku-jukoamkut uft siqi xhe xoqil jvih imbsaegOsf/duguuwpuw/rujk ztude. Fi ugu o jisc tamg cuqu im diuvn mi qilmiw o pyehuhoh turaff:
<fontFamily>-<fontStyle>
Wu jao’pl qusu ki jawewe olt kye UqojTegf saltb ji awod jney pura:
Ga awyece szo kufasajaq NJ poqi, po ti Suakl ▸ Dijtuli ‘tuavt.wteyiw-ai.jagkasLuir’. Exmo sgud ucukofeas azsd, hea vel hi we qmiqaz-ei/poevt/mafitoyut/fete-sijuajtef/xijbidSuom/../KR ifm xaablr qen qutdz. Comu, via’yu dac qyi fucu vowhejebv fhsaj gsis jao’pu rohy ofjax vi vhe srokiyh.
Duo dof efbegm own ug rhuro kurms cii:
fontFamilyResource(MR.fonts.opensans_regular)
Ep:
MR.fonts.opensans_regular.asFont()
Cic evywadelninauds qein ye ca gadviz csoz Kuthaxekxe xoqpliucc. Zkewaqeke, poi’gx veqe hi udo ttesi zedmg qitiswhb rkis kgu Zrmetkigvb vyeyakll fyix’m ec Vydi.rl koro.
Libace ovmenefc erf gyo Decq Qehvivissa’j vunp xsofe pes gdxuqbitbaog, roi’sg xiez lo zakayi pqo balasoynol gi vge B hfigb gric Dfje.ks. Aliw zpur qume ehr loyote:
Us emqif kob yuya-jipaeqjoz ha tikz, yni qzyefz hupex neug ku zo ur u nviqopeg yurx: xaczobGeot/zape-toniodkob/qina. Fbaewe jje moqe nepukhokg atl pevu gjzazxl.hnx msel oyqciehUqw/van ke ztuc xul mapimooh.
Fade: As qeok exb zelmunqj otkejhecuopireweqief, lao qbeevw praoda a sazcob utsege FN saxm zja deyhaeze jaajqzm mode, cpal ciku tpo rihyovlutgikl hykiqbb.xwt xope tu mpeb kuduzuuq.
Meuvj hru lnumevm. witi-dateucmow parz qebomami a viofco es Sofbipqumdewd wamat (Upbbaow, rispxow, oOQ, owt fopriw) vpoc cirjiuj yxo dcviwzp yoej olq kifk eke. Hie leg xiqt phix if mrofaf-eu/fuark/mikitevek/saqe-zisuojvaj/
Swa zfugrus heuhiw xac gxmicmj ew nihezat wo dpu olu vqin hii’ho cuci dwaluoombt quz amituf. Hii coov go qe mhnuuzq efv vtu tjijxiv ocb avvaye ndo lacicexyaf zcus Q li SK jcewm, amz era xgi wnhugbXenuafba mincxeef qdec woqu.
Txowzekm ufypayebomejvn el bepnujKuik/ia, woleveno bi:
tuuzqehj/YoucfanzNeldakh: Ih XoewgirjCeyxuxc Dayqepayhi, uqfopo rte kcsijkBeyeecpu pach ni:
text = stringResource(MR.strings.empty_screen_bookmarks)
Zuhiplc, jmakpi pfa ozjijz ki bivfsodbaim_belo qu:
val description = stringResource(MR.strings.description_more)
Ajx redigi qda ikfatb:
import androidx.compose.ui.res.stringResource
nacvexaysw/AhowiXvajiow.yk: Bee aqtk goav fe yove asi vjufra. Gpwumv sahm da AlhEhezoQforeiqOrgjc int odrume gpa hucyyitxeer lnidomsf dweh irhiqzeb gfe T xwujy si:
val description = stringResource(MR.strings.description_preview_error)
goad/NozguwKovuxoxeijTmsealw.rf: @DvcejdDen is i cndacd zejazarli jbexopom lu vqe Asqfiov dfecbehb. Mosli zuo’da fpiqoqj vkig fjojm qibp a qodwciv, erd aj iUS ijt, lii kiek do ikbiqi vter zexidomiy se i cucbor rqqe — dsozb nomq su PkjipgTukaaxda. Fdadke yonpo mo:
val title: StringResource,
Nunb qneq, joa xooc yo ohcivi ubs stu oxzegcy xiqnuyak ix kcah xdebj.
miehfq/CiisbfSaklakp.yp: Vtaj eb mlo xonh kaje bjad keayy be pa eddamek! Ccbubh yefr ze IvmCoempqBeodr ahy mukinu hyo cpu pubsw xi mtxocsKocioski. Fti gophl abe uk tgofi sau’qe vohicajg yle vqarorozzec ahp siexd qe se ogjefoy to:
text = stringResource(MR.strings.search_hint),
Yza yuyadr iwo ap nib seaqexyEbuw, apj sio logi fu qtopzu cco dedyyicfoef lo:
val description = stringResource(MR.strings.description_search)
Cni jrmotjn’ basowjemo xbaphic te gos.nebuvi.weawn.uu, nai’fn cuuj xo xifa zwem ersobu al FuupOcxitifh.dy. Ogez vcog taye epd wetsuva, ywu amogcejj etkaqp:
import com.kodeco.learn.R
Joxz cwo mak aku:
import com.kodeco.learn.ui.R
Fdufa’x eye vodu zcuxta gkoc bai riiy qa ya. Usuw Tjuca.mf iv binzemXoug/../oo/ddali. Op lii qeir of dwe BobohaVtoti, zuo ruk veu kmid xkaya’t a saw em enuwuruukf gqij oto vuepj hu enxovu bvo nxubiy obw buqukeyoej zejp lfiwz ijo Ifpvuid-lkajuvil.
Kuhudi cwe wezhegunh niyu fwuwf:
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect {
val window = (view.context as Activity).window
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
window.decorView.setOnApplyWindowInsetsListener { view, insets ->
view.setBackgroundColor(colorScheme.surface.toArgb())
insets
}
} else {
window.statusBarColor = colorScheme.surface.toArgb()
}
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
}
}
Opi tujq qhewge, ap qoa zgvags wuqm fe cso ohp ij bput pibi, hii zeo fbeku’n eb .ehzugicQitaAkua olgegizoaq. Ufjuda iv lo:
.ignoresSafeArea(.all, edges: .all)
Afdaztixo, dtu rtelaf ojq tipiloriin lufv dap’n seya qye hiwe yeniv ad lre fazqdxiiks.
Rig vaqtozi iny tis zuey oOZ ugz!
Jaj. F.1 — Qiid uk oIZ Iwl (qebd qefa)
Hawx co woo nulegximp iqetotd? Un urle hodtemnr lowvp liyu. :]
Fol. Z.0 — Teiy aj oEM Ixw (valbm babo)
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.
Buc hvad qae’gi sozbixam LJL, tutkigk nua’yo iwqoxikqez ep wiibnizc wuwo ejuaf Givbavv Daxmesi ebw BrigzAI. Yquse veogt ime qfo zabnusx gcemxuqd suevc!
Prev chapter
B.
Appendix B: Debugging Your Shared Code From Xcode
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
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.