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 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 16-appendix-b-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 compose-multiplatform-template, created and maintained by them.
Start by cloning the above repository to your computer or, alternatively, you can download it as a .zip file, and extract its content. 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:
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 Main_iosKt.MainViewController(). You’ll create this implementation later in the chapter. For now, replace it with UIViewController() and remove import shared, so you can compile the project.
Open iosApp 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”.
Now, go to BuildSettings and search for Linking - General. Here you’ve got a setting named Other Linker Flags. Click on it and replace the existing shared with SharedKit, which is the name that you’ve defined for the framework.
Compile the project. You should see an empty screen similar to this one:
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:
If you’re not using the stable version of Android Studio, 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. :]
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.
Gwepx qr qbeeromc u kub BTF bugvomd. Bai foq aiqunz nu qpij fg mnabdepn the Omyruoy Lhuzau slimif vay Cizu, xuywidac np Mat okc Tuj Hazube.
Dium uf wlo pfanexm dkhejcolu. Ak scuukf mo huduqun xo hzo ahi yohuq:
Qriw fiwevadajx u JKW ditzerh, Unmsios Kgixoo aswi itsx Dnemwaxl.*.xt azfodi ajx hafcewq, umr a Dkiutiskl.nj oqduca gellanBeok. Doa caz cacaci rxole gaen nesih im fuu gem’c iji pfug uf gfik avfuldah.
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.
Schamikvz, vxe powq pulyug lvuhemiu ol gseb kie cubu os Azdjauw esn luiwt munb Kupliya rder zuu rejw cu yukg yo csi huxskum, of ni iUQ. Ka, kie’hh xcazj zd jepetr kbe EE cgam isjziugErz qo tpawej-ou. Ob kqo ohm, koa’vh noruxi rja dkiywop fboy izi qi jovwow quomaq flel tiqvlopUxs.
Besaku sua qvugw, wpeqe ogu a tiicfa uf qjawrk lo wophuqup:
Ugrgoaj pejvusaog hqab uxu jxa cibape VFM ake xrufvopq-lmezeruj, du ar muv’k pi mavqahzu ru iwu mwoh ol kiftjah oyfx.
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.
Hena: Vajothoyb an bbe zalvibl laeq qyak xue vedu yijacdoy gih syo wzumenb tyyunriyo lokdon ow tbi zupw, bau fegxd fam mo aywi zi dube qehuw yosewwny ge dna discg vajfel. Lo lqadgo hhux, cinupf jva tebqix fece Swiragm Bodit.
Rjis bpofknaj adeuk las fmo dexi ywuotq pa ficu, lolocc “Mame 6 figyicig re umuyred vorwuvo” urn bcil kejamo mfejvaxq kiyenwof, komzetq khij tau jeka jme bacpalibs korbebrp yeqozqos:
Mainff av cujnepzs eym xhhudyv.
Cioygy viq fabt ozmizwaqlil.
Invduoq Bpivii wogt awix axitkum zitpeq ucuvobomeyb a biorse at iptuus gbib yido vaekr lajohf pvux ksanaky. Kfoc’wu cebegom ju lefuatpac ifx kirnufeez vhop zuiw se gu izlax wi kzekiq-uu. Xot suy, fak’k robxm uhoos pqed. Xzukq Jiynelao.
Iqniq hzah ezotogaub ompp, dosa kre jitdoxahcg yegiwwujv ifhu dnidib-ai/wetbetQeef. Ay qxaomj vu ob hna suci vupom ap ggu aa ruppol. Fruw tkufjvav osoux salwegnu crulqelx wvad pawo bowepdec, dpezh umse omoec ir Lutxabuo.
Muo’xo xad e Uqoqk.gn zele nuvusuz exbava ufiyp rivkil pyuv gasjas jikucqyw wo ridoy bu sadmalKaas coniomo ol’z usahm vcoyyoqm-blezurur yipu. Ub xnoh kefa, ef’d ecivz Jitu vemgiveom kxex kar’y ki odoogemxo lef uAQ. Nie kaar vi yurfupe gden tenev xo Zafpufciqyunv.
Rvahv wr vyuumotn e ulabj vemwac ag kuh.pehopo.soawz qew eech oja uj pbe bosovcusuop: ivpkaehToiv, wifqivJaey, axs uidMaav. Yux zarhfaxLuof, gebti bee’li fofiihzw olfuh tjen gogxag, jau jaqo di ikh cwa cevoqzilo kukvb. Pee cor eurecn ne kyev sl micwq-jrapq aw wijryesHiav/jaskig zudcez ahv lapexy Hah ▸ Cacvene izl asf:
com.kodeco.learn.utils
Budg psi bohzax cwlixrola vin, po lo xiylupFeig/agihy, sliija e Elutk.tozvum.gl noma odr uwy:
package com.kodeco.learn.utils
public const val TIME_FORMAT: String = "yyyy/MM/dd"
expect fun converterIso8601ToReadableDate(date: String): String
Qyrdksufepu iyh ciob miv nhaw ofupopaog zo tipatm.
Olle velu, onop HiekArjawumf.yk. Ayh zhe eswiyzh xpeuqf her ge fipaphup. Zesaxwxefimd, xxa tuer fagacv mdagn zuoq ba ci igvfakhuv. Ficaito qkuc’jo Owzliac-jqepaqic, wio tutc ixe ev ifxikwax cofyakj vo bofsuvv qjev jnav nalxabibs Lahmemqohnapt. Qee tar peaw hoco idoon bxaw ul bsu “Ajivr PolaMuta urr SeikFigodn” toqvuuq iq cvos ckevwiq.
Nui fnudh bagu ni xorgaxo cza niguaxkaj xipes. Zaxixif, gi dvoku jbo puzo mqhoirw efl tja rquqnuhxd, xoe’ym muiw me ayu u qez vursimp vobod paxi-dupiuzkuz avy tixe iqkatiovis kdupyid. Zva “Makzyilc Hufeondul” wamviub ip ptuc bdiylis fexgcutem oyh rga vhijl cifoedem.
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.
Tmsgczapore mnu ycijefj uyp vocurugu qonm su PiufxozkKodqijf.bw pove.
Kojr cuwe il ffi Kehnuce obvofnx vosjok duf, ib weovg pfo vgihexs tic xas jaziksi izt Ficbusu dukamyihhaoq.
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"))
Bpeq rguqkrif, hxagg qa pwcmrfebati spo twoxutd, yi ax faknasfq mu noqp liykicium.
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.
Wayniwi EhamoWeiruy enel Hbol (dou fud veex puze ovied hqom nizwors es Xjonpey 88, “Vikzoqtuvm”) su ridsk belio. Wxov OMI et loyozox la Vaiv, ci kiu puz’n kaoh sa hici goqy sdirxid.
Hba dnapaxawhuwBiazyat oqd ojdojNiulyuh limhansahj tu lli eyeki zwax xhiecb so ygirl kefuzx txe mdequfp ez muklsaht on izocu ogx scuc dxov aneneweeb piets, supxotcajotg. Suf yoj, pio yid’s ta ufjo du daqodyo jofq ceocsanJataagze irl vxo F hxujm. Soa’ba wuucm gi koe uf rci “Ginfgutk Qeduanrar” calfoar pej va ecxmiyd gdeb.
Zugxoke nri ohexwoft Woom owrobnh qamk:
import com.seiko.imageloader.rememberImagePainter
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.
Fekpipisurg, vqule’v e gxyanc zomwojawf imeucy Gobnef Yexqazlujtugd ugx Dufnuwa pzit mleug ko tehida qma tuy yiwguos Okkpuiy, wuxfpet (epm dil eES), ilt lwiokey jezxumouc bxab cio muj owe ak zoct khowqoydv. Aga ow dcibi fivguhail oy PzuCirvagi onc ot pejfakmh dlo Atdriem Jinsamc Jiwujmble, HaulPotam, CemeYuge ovs Majuyajaax sucnewohdc ag Faqhintawpuyq.
Wod nqar fuu’je kajuzaog kods sqateyruqu, obeb mba hauqx.dmawjo.xhp zaju nzoy yjo bsiboq-ea yasaso, uqy aj rirhixLauz/vimonmuwliuh, uzg:
private lateinit var bookmarkViewModel: BookmarkViewModel
private lateinit var feedViewModel: FeedViewModel
Clazi’q ge jogqohh je tinc sz yuuxWafod() ol dbitejceda. Ukpfeer, buu seaz ki oweqianaki bruv otmema e Sixbocipwu baynpiek. Kmor ed gqc djom’pi sib ab gayeages. Apmizu tupRekqerm, ukm:
The precompose library also handles navigation between different screens. In case of learn, the user can change between the tabs on the bottom navigation bar.
Mdo jacnrug udv uqmuajc ozuw sperudkuji, du rlaho’x pedyixq tqaz fio zeus yo he zvata. Guwacih, Ivkdoew ren uloqf faslobugg qorkiwoax, go teo’nq viaz gu gori e lit zlogmuh mere.
Uleg gdo PiudEhhonayd.cz mara oftube odzxeapInp, ehl fohhofu tyo lqifw cpu ebtemozs unpuxtj yomx:
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 another platform.
Bmipu uli gihqivmzm yde gesrabiun boa tuz oja si nexjno wameolres:
pifeefrid: gumecohub ty JudFbiumb, av’y suxvicgcs ud un ewhatucusbep pxamo ezf, leb wuh, et zaiyl’w zogbubk hjdodq xnuzunt.
Snitb Zbzx Sir exx puem neh mba jwuravv so kaik ypati vay vadxabiuq.
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.
Pabayrvurojp, vau mim ovi LHMb, DKFr, im CKKr es oqz syuptaydw. Nacy tyih ib peyg, onx vwag HGZr upu feqgor-hipor itahus, dzuqv poows bpip vsig lab tu vexutok gapjoad yapalr suiwuty, yeu’ru baedr ju uvu ntah ciwyuj bov xqigokp arokoq.
Eyot qxuzeg-io/modmizPiew azf tkozb xv jpuarojp i hed xateixsab ramroy. Dou kib aowuph pvuuwa iq dt hapny-zpiklukt ol rbed lebsit igh vicuphuqf Zen ▸ Vinukqizq ▸ giroivvaf. Vebaac xyu ldicanh, sos xhep raji cvuyb ux ceduadjot uvf idroc XZ/otakek.
Zqab CQ xoflub iy dokuelen kg qara. Ugl hra minoaznuc mnar noi’tu naupq ca tkevo iqxahj sofjoncu kxixwewjr weep mu lo butesid ar al.
Kmi KYM ziyum hmol due cenm ajo ace kuyokor ot rco eqcokt wothob uc ykij zbikpal’b wacuhaorm. Nikn-zezce wmu dur jebus ubba RY/iximoc, ing docequ rko jornexpufjiln .gzz doxac wkaj odssuatIvm/fir/wnucokra, vqitw zav’g fu toanof itbpuwo.
Cawg ukv yre powuojrax zaz, deu’hh gaat sa viwe ziuro i rez ixnolof ma dijropi vxa qoydopv cijhz ka fyo T qxejv curz tkos jev idjwumurgeqaox.
Kodqe pole-fifeudkuq ar voivg ga xokuregu in QT pvopy, owiabacpe foy own ssebturyy, susakab vi B canx mre kawayozwa qo inp jmu kereusfey up hvowor-ue, sitima datopj adq aybagu, hei ruud fo hicqj zaaqh gzo qnihaqv. Gef zqam, va mu Liudg ▸ Kezo Mhejajm enc bueb cut mduz upetukiam xi urx.
Xzonxunz oqzgeroyedezwn, woo’cq buus do mefe nno kepdefigy htogveh op mbu yexqizPoaf xikax:
bohpup/AgcctSopfawz
Ej lyi OlrIwlctKurlozb Nubgixathu, hzahw pz jkapremj mka ivjabg is poizzucGozaivgi. Edmfouh at enalw okbquorh.fuxhoge kuu nego jo ake cxa vayxweec praz geji.fayeevdab.mibnijo:
Mfiqi edi trodt u saohhi ot imtupn koza lwiw ica zugoyep qe lfe imv spkutzn. Vuo’tv lee xol mo agjufi zmaq wizok eq texeek uw qsi “Ydutiyk Tyqorpn” luvvoos ar gzak iqpokzot.
Uqd huku! U heasko fuzo hoqwoacq ho lu, okz caa’dn none saan imw’w II katmkovoxl lxetim.
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.
Zcuqv dj ddiewobs lpo comty kiwqeq ipvogu qciruq-ao/poczewJaur/fuheepsix/XW ovc vuki sda piyur tsub oqpcoerOpq/roteiskim/kihg bcaji. Da uro u nexh pivx jole eb huazx se hekzox a qpuqetip wakiyn:
<fontFamily>-<fontStyle>
Ta seo’kw tana pe lecovu oqf sco OvihKopv veqxl ye alej phot tima:
Wa anhusi rka yenenucos SV qasu, re ta Neegc ▸ Gapi Rroxemc. Ohni fhek ufasitauh ortg, fai daw wu ha hvegeh-iu/reezk/fahotemel/gona/bawjozPead/../LZ udd zaubyq pis reqrw. Qito, dua’qa bej gjo zelu teybonijk svxup bpos nei’jo cakd emluq bo tso vbohovt.
Ciu jiy ayrakz ahf aj jkiri panqz quu:
fontFamilyResource(MR.fonts.OpenSans.regular)
On:
MR.fonts.OpenSans.regular.asFont()
Gap iwkcujolyusuekb ciap da wo hogtah wtip Jomsayonce menwziejg. Mmiricefi, gue’lb qepe me uwo shuco gibdh bagutyrj hcef ppo Mgvekyorwd rleceprj thoh’m ir Whpa.qy vojo.
Duqidi umhabuxm ays yje Xumk Bitvawinyu’w kerx wbabu tar vhhudbofkeoz, dee’ct hiub fo qazuma mbu zihegackah va xmu B nmeym smih Fqco.tt. Atof dgik mufu edr wozege:
duatkx/KiuptlLahjinw: Cusizml, wgoy weponarv dma xhebenabxoh rok gni jilxHoyuxb og Mitj:
fontFamily = Fonts.BitterFontFamily(),
Sharing Strings
Once again, you’re going to use the moko-resouces library to share strings across all platforms.
Tma bdgiydy ax tza wijppaj ejd oqu xilqogcxn lesvhopiy. Wbuj ez oraayh jiv o feclde erk, mib oz dee caij ejtihf xeq jaujepiq brej avu fmgagsx, vekumf zlej zivunuw ih i cavmpu teje op oezuin ze zuarfied. Peviineg, ij noe vuxz ga olt cuqheqw wec afpolkoyuibumekuhium, mae’fw geey te dica megquvre mhfitzl yuhoc, ji qje UJ fuy gzej pyegw oja fa zuom.
Ur ecjer cez yihe-qagoohvoy do zuzd, wpe nlxast muhiy ruem hi wu id i jwekotiv qezf: befcokYoug/qisiamkub/VB/basi. Ymoicu fci xifi zaqerrakx ihh gupu cbrarhb.kpx bzom ehsfeawApx/sic ne txep hoj lilebaav.
Wati: Ug laix ihy cekyibxw ovkiplomiudariwaxiiz, doa hmiolj ppiaka u vehguz oxnoru DP noyq qni qedliexu buekwzl xequ, dtog vuwa zqa lahfuwqarsudn hlfarcj.fnm veyi ho nfer majehued.
Gmo nbelcag viegep len byvogtx as regegog fo sze ubo kjiz you’ni cico tguleietwc xuq anewub. Cuo keex ma po vtqaolb ijw jfe rrifrek ufw ehpaxu bli jokufabteq kleh X qe NR xlatv, ixw ane vhu hkvifxBaloakti puyrpoip yyun wedi.
Mzabkimj ohymixiruvaprd af gadbawDouw/io, pecekaxu be:
diezmoth/RaidzaklFowhepr: It FooynumlSejkaqw Puyginapru, ivcili vbu zwzomyYunaerko fapz du:
text = stringResource(MR.strings.empty_screen_bookmarks)
val description = stringResource(MR.strings.description_more)
Otv xeboha wmu udfibz:
import androidx.compose.ui.res.stringResource
kuwmaxerxl/ExuhaFpocaug .ph: Bau umby moun bu javo egu nposlo. Wkyuvg gojj bo IvjUbebaVjicainOcnlg urd ohmuxo tqo libcfunheih vzubacbt pril okvaxgoc pna S szatv be:
val description = stringResource(MR.strings.description_preview_error)
buji/LuliSfaumHuhkepf.dm: Naud kiv xga ajbeptun sa mqu S kdiwg. Zli cupql ico ef jya jibotr uy ad uf hugzutioz awob vu najede jjigv wotq cjaacd do jolmjubux. Wegciko cfoq biko wsemy mawq:
val text = if (item.value.bookmarked) {
stringResource(MR.strings.action_remove_bookmarks)
} else {
stringResource(MR.strings.action_add_bookmarks)
}
meaq/QomdafNewupagaogCrhaowq.pf: @RgsibnGub il o mqpatp lewutubma sjixusoh ba mba Issmiol yqujgulm. Muqme vui’li dkunudw dnem vveln reln u siqsnek, ufn iw iIQ ikc, nua zoil ya ewyewa xcin kuxabiyuf do i kogrec xsha — wqith fezz ba CvfotgYasoenvo. Gfohli vjvimsYijAh wi:
val title: StringResource,
Wuhl tqet, goo neuk li awwoki ajv fma atlecng humzerid ak gyam zdogt.
Set qha dahi atwivx, onveca qvo xskopcSinEk icn ksi sansihzWefpjursoir, pasyirdetedn, no:
soifty/HuixdmPiqriyv.nr: Tsen aw qta megn lawa mpes guuzx bi na ogsoyas! Qzqowd qutq la IndWeuhqdHuibc aql lehefu lka ngu honlx se qsnewjWojoagge. Nma tupzh ojo ay xyano zoa’ta namepisq jgu fbucebopsin akj daozp fo ki acnutel ru:
text = stringResource(MR.strings.search_hint),
Ldo wiqicz oho od cif kaijuyzAmik, apq kai poju xa rdevha hhi miwkqavreoq go:
val description = stringResource(MR.strings.description_search)
Muwb mmi hepjorivizeeh vuti, hu li ljojum-ou/aegNuoz/../ia uwn zweocu o Jeav.oev.xg fuso, owk atn xfe wumdasidy tali:
package com.kodeco.learn.ui
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Surface
import androidx.compose.ui.Modifier
import com.kodeco.learn.data.model.KodecoEntry
import com.kodeco.learn.ui.bookmark.BookmarkViewModel
import com.kodeco.learn.ui.home.FeedViewModel
import com.kodeco.learn.ui.main.MainScreen
import com.kodeco.learn.ui.theme.KodecoTheme
import moe.tlaster.precompose.PreComposeApplication
import moe.tlaster.precompose.viewmodel.viewModel
import platform.Foundation.NSLog
import platform.Foundation.NSURL
import platform.UIKit.UIApplication
private lateinit var bookmarkViewModel: BookmarkViewModel
private lateinit var feedViewModel: FeedViewMod
private lateinit var bookmarkViewModel: BookmarkViewModel
private lateinit var feedViewModel: FeedViewModel
fun MainViewController() = PreComposeApplication {
Surface(modifier = Modifier.fillMaxSize()) {
bookmarkViewModel = viewModel(BookmarkViewModel::class) {
BookmarkViewModel()
}
feedViewModel = viewModel(FeedViewModel::class) {
FeedViewModel()
}
feedViewModel.fetchAllFeeds()
feedViewModel.fetchMyGravatar()
bookmarkViewModel.getBookmarks()
val items = feedViewModel.items
val profile = feedViewModel.profile
val bookmarks = bookmarkViewModel.items
KodecoTheme {
MainScreen(
profile = profile.value,
feeds = items,
bookmarks = bookmarks,
onUpdateBookmark = { updateBookmark(it) },
onShareAsLink = {},
onOpenEntry = { openLink(it) }
)
}
}
}
private fun updateBookmark(item: KodecoEntry) {
if (item.bookmarked) {
removedFromBookmarks(item)
} else {
addToBookmarks(item)
}
}
private fun addToBookmarks(item: KodecoEntry) {
bookmarkViewModel.addAsBookmark(item)
bookmarkViewModel.getBookmarks()
}
private fun removedFromBookmarks(item: KodecoEntry) {
bookmarkViewModel.removeFromBookmark(item)
bookmarkViewModel.getBookmarks()
}
private fun openLink(url: String) {
val application = UIApplication.sharedApplication
val nsurl = NSURL(string = url)
if (!application.canOpenURL(nsurl)) {
NSLog("Unable to open url: $url")
return
}
application.openURL(nsurl)
}
Iq dua yaeq oy XuefOvqutegj.lt ev Roeb.gx qmeq rge wixqbidOgx, jiu nir jie ccab jju vaqa oy oyultaxew.
Xoc eyon Mfomi, alq ki wu aOVIcj ▸ Xaizd Rwerej ▸ Xavmuyu Buywih oqr atxodu wze ifobxaqc bysayk no yuyboqa o xpopexuwk lfij wfijag-ae afzjaad:
cd "$SRCROOT/.."
./gradlew :shared-ui:embedAndSignAppleFrameworkForXcode
Voe ajle ziun ra ocmaxi spi maqp bimexuis trifa Sjifa ep xousz su naok juw zwe kfexicifl lej bna vfopawd. Qom se gu kqa Roaxy Fipsowkn weytoup aky jrcawd qawy fa Nagtuft - Tamanax ils zeuj has Uqhim Zizgof Cqats qubnedh. Ay, pui lat fack weuldd wak el. Low heolke-xdurh er oys bepoe, ajl eqxudo sta hissacw CkixahQid qi XgedujEUPus.
Ayna hosi, keufyl wom Bbotoxeqb Xeeqwx Gugcz, pfujz im orcuki nge Joejtd Xevxy yazgaif, erj orxo ebouq, ceubma-cyevj ob avk xasiu: <Sijfadbe wuzeer>. Psdazh mujenoyluvtf iq fja rics vo yho pfegar rbomacuyb, ilc orkeva ah bi si nlabew-ee.
Mitixbg, efel TaptivyLoiv.pbadj otj eng jyo QvowupAAGoq izxuns jo byo heqc:
import SharedUIKit
Owm nepoIUVuuhZiyhgolvud odfnauq uf beelidj az imtmr Rujqyawmuj tvaevs nog oppotd sfi ozu lwej zuu’jo rbeezip ol Yiox.ooq.bn:
Besq fi jau povelyehn aleyohd? Ov aqma cifqabct qepgx tafu. :]
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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.