Blogi

React Nativesta kahteen natiivisovellukseen muutamassa päivässä

Mikko McMenamin

Mikko McMenamin

Senior Software Developer

13.10.2026

React Nativesta kahteen natiivisovellukseen muutamassa päivässä

Olin käyttänyt React Nativea sovellukseni kehitykseen yli vuoden verran, kunnes sain tarpeekseni.

Sovellus tuntui Androidilla tahmealta, käynnistyi hitaasti ja osa karusellien animaatioista nyki. Jahtasin jatkuvasti riippuvuuksien aiheuttamia Sentry-virheitä, enkä saanut sovelluksesta niin natiivin tuntuista kuin halusin.

Julkaisin SmartRepsin tammikuussa 2026 ja olen kehittänyt sitä siitä lähtien. Se on salitreenien suunnitteluun ja kirjaamiseen tarkoitettu sovellus, joka hyödyntää tekoälyä. Päätin korvata React Nativen iOS:llä SwiftUI:lla ja Androidilla Kotlinilla ja Jetpack Composella. Claude Code ja Codex tekivät suurimman osan koodauksesta. Varsinainen toteutus syntyi muutamassa päivässä.

SmartReps on keskikokoinen sovellus: treenien kirjaaminen, treenipohjat, tilastot, tilaukset, tekoälyominaisuudet ja alustaintegraatiot. Siirrettävää oli siis vähän enemmän kuin pari ruutua.

Kahden natiivisovelluksen ylläpito olisi aiemmin tuntunut minusta liian työläältä näin pienellä tiimillä. Koodausagenttien myötä yhtälö on selkeästi muuttumassa. Käytin kuitenkin paljon aikaa arkkitehtuuriin ja spekseihin ennen kuin päästin agentit toteutuksen kimppuun. Ilman tätä valmistelutyötä migraatio ei olisi onnistunut näin nopeasti.

React Nativesta haarautuvat natiivit SwiftUI- ja Compose-sovellukset. iOS:n tilastot ja Androidin demoprofiili näyttävät treenidataa, asetuksia ja Health Connect -integraation.

Arkkitehtuuri koodausagenteille

Halusin agenttien pystyvän käyttämään sovellusta ja tarkistamaan muutoksensa itse. Muuten jokaisen ominaisuuden tarkistaminen kahdella alustalla jäisi edelleen minulle.

Molemmissa sovelluksissa on käyttöliittymästä riippumaton ydin eli headless core. Se hallitsee ominaisuuksien tilaa, navigaatiota ja työnkulkuja. Toimintaa ohjaavat säännöt on erotettu domain-kirjastoihin. SwiftUI ja Compose näyttävät ytimen tilan ja välittävät käyttäjän toiminnot takaisin sille tyypitettyinä komentoina eli intentioina. Näkymät eivät käytä backendia suoraan.

Kehitysversioita voi ohjata myös komentoriviltä. Agentti voi aloittaa treenin, siirtyä tietylle ruudulle, lukea sovelluksen tilan ja odottaa esimerkiksi treenin käynnistymistä:

smartreps send session.startFlexibleWorkout name="Push day" --remote 7411
smartreps wait workoutTab=activeSession --remote 7411 --timeout 15000
smartreps snapshot --remote 7411

Komentorivin ja käyttöliittymän komennot käsitellään samalla tavalla. Komentoriviltä treenin aloittava testi käyttää siis sovelluksen oikeaa työnkulkua. Snapshot-komento palauttaa samat tilatiedot, joita käyttöliittymä käyttää.

Natiivi käyttöliittymä ja agentin komentorivi käyttävät kummallakin alustalla samaa käyttöliittymästä riippumatonta sovellusydintä.

iOS:n ydintä voin ajaa Macillani kehitysbackendia vasten ilman simulaattoria. Androidin Convex SDK tarvitsee Android-ympäristön, joten integraatiotestit ajetaan kehityssovelluksessa emulaattorilla. Molempien ytimien testeissä voi käyttää myös oikean backendin korvaavaa testitoteutusta.

Tätä tarkoitan agenttisella sovellusarkkitehtuurilla. Agentin pitää voida käyttää sovellusta ja tarkastella sen tilaa. Se otetaan huomioon jo tilanhallinnan, navigaation ja käyttäjän toimintojen suunnittelussa.

Käyttöliittymä kuntoon

Vaikka arkkitehtuuri ja toimintaspeksit olivat paikoillaan, ensimmäisten ajojen käyttöliittymät eivät olleet lähelläkään alkuperäistä sovellusta. Ydin alkoi hahmottua, mutta käyttöliittymäkerros puuttui vielä suurelta osin.

Sisällöt, painikkeet, asettelut ja käyttöliittymän toiminta vaativat vielä paljon työtä. Pelkkä ”tee tästä hyvännäköinen” ei anna agentille kovin paljon eväitä.

Ohjausrajapinta auttoi tässäkin. Agentti pääsi tietylle ruudulle tunnetussa tilassa, otti kuvakaappauksen ja vertasi sitä alkuperäiseen. Sen jälkeen se korjasi erot ja otti uuden kuvan.

Kaavio vielä puuttuvasta käyttöliittymäkerroksesta toimivan sovellusytimen päällä. Nuoli johtaa toteutettuun ruutuun, jossa näkyvät puolen vuoden penkkipunnerruskehitys, ennätys ja sarjahistoria.

Tämä on helppo aliarvioida agenttien kanssa kehittäessä. Agenteille on annettava mahdollisuus myös katsoa ja tarkistaa, miltä lopputulos näyttää. Testi voi tarkistaa, että oikea ruutu on auki, mutta se ei kerro napin tekstin huonosta rivityksestä tai kömpelöstä asettelusta suuremmalla tekstikoolla.

Testejä, joista on agenteille hyötyä

Lopputulosta tarkistetaan usealla tasolla:

  • Nopeat tarkistukset: arkkitehtuurisäännöt, kääntäminen, lint, generoidun koodin tarkistukset sekä domain- ja tilanhallintatestit.
  • Toimintaskenaariot: yhteiset testit käyttävät sovellusta ohjausrajapinnan kautta oikean kehitysbackendin kanssa.
  • Käyttöliittymän tarkistukset: napautukset ja eleet oikeassa sovelluksessa, saavutettavuus sekä molempien alustojen kuvakaappausten tarkastelu.

Jokaisella on tehtävänsä. Sovelluksen tilasta näkee, onko treeni käynnissä. Kuvakaappaus näyttää asettelun. Käyttöliittymätesti voi raahata liikeriviä ja tarkistaa, että järjestyksen muuttaminen toimii.

Koodimuutokset ajetaan oikeassa sovelluksessa, tulos tarkastetaan ja havainnot ohjaavat seuraavaa muutosta.

Myös virheilmoitusten pitää auttaa korjaamaan ongelma. Kun testin odottama tila ei toteudu ajoissa, ilmoitus kertoo, mitä odotettiin ja mitä sovellus palautti. Agentti saa näin lähtökohdan korjaukselle.

Nopeat tarkistukset ajetaan jokaisen muutoksen yhteydessä. Öisin ajettavat E2E-testit suorittavat yhteiset skenaariot, natiivit käyttöliittymätestit ja Maestro-testit molemmissa sovelluksissa kehitysbackendia vasten.

Tekoälyominaisuuksille on erilliset evalit. Treenigeneroinnissa arvioidaan esimerkiksi liikevalintoja, kuormitusta ja progression sopivuutta käyttäjän historiaan ja tavoitteisiin. Tuonnissa ja pikakirjauksessa verrataan tulkittuja liikkeitä ja sarjoja ennalta määriteltyihin odotuksiin.

iOS ja Android samassa linjassa

Molemmat sovellukset käyttävät samoja Alliumilla kirjoitettuja toimintaspeksejä, generoituja backend-tyyppejä, ohjausprotokollaa, testidataa, skenaarioita, käännöksiä ja grafiikoita. Swift- ja Kotlin-toteutusten moduulirakenteet vastaavat toisiaan. Agentit löytävät helposti vastaavan toteutuksen ja testit toiselta alustalta.

Sama skenaariotiedosto ajetaan molemmissa sovelluksissa. Testit tarkistavat, että sovellusten palauttamat tilatiedot käyttävät samoja JSON-kenttiä ja tietotyyppejä. Työnkulkujen testeillä varmistetaan toiminta. Alustakohtaiset erot kirjataan erikseen.

Saman demotilin tilastonäkymät iOS:llä ja Androidilla: neljän viikon lihaskohtaiset volyymikeskiarvot ja viimeisimmät ennätykset. Puhelinkehykset ovat yhtä korkeat.

Jokainen sovellusmuutos tehdään molemmille alustoille samalla kertaa. HealthKitillä ja Health Connectilla on omat natiivit toteutuksensa. Agenttien ohjauspalvelin on mukana vain kehitysversioissa.

Toteutustyötä on jonkin verran enemmän, koska muutokset tehdään sekä Swiftillä että Kotlinilla. Yhteisten speksien ja agenttien ansiosta käsityötä tulee kuitenkin hyvin vähän lisää. React Nativekaan ei toiminut identtisesti iOS:llä ja Androidilla, joten molemmat piti tarkistaa jo silloin. QA-työn määrä on suunnilleen sama.

Mitäs hiton järkeä tässä sitten on?

SmartRepsillä on jo yli 1 700 käyttäjää, ja määrä kasvaa. Halusin tarjota heille parhaan mahdollisen käyttökokemuksen ja saavutettavuuden ilman, että tuotteesta pitää tinkiä frameworkin takia. Natiivit sovellukset kaipaavat vielä hienosäätöä, mutta konkreettisia eroja näkyy jo.

Käynnistysaika

Mittasin jokaiselle sovellukselle viisi käynnistystä iOS-simulaattorissa ja Android-emulaattorissa samalla demotilillä. Käyttäjä oli valmiiksi kirjautunut sisään, ja sovellusprosessi suljettiin ennen jokaista käynnistystä. Natiivisovellusten aloitusruutu tuli näkyviin nopeammin molemmilla alustoilla:

Aika käynnistyspyynnöstä aloitusruudun näkymiseen, viiden prosessin kylmäkäynnistyksen mediaani. iOS: React Native 5,20 sekuntia, SwiftUI 3,66 sekuntia. Android: React Native 4,03 sekuntia, Jetpack Compose 2,23 sekuntia. Simulaattori ja emulaattori, Release-koosteet testikäyttöön.

Latauskoko ja muistinkäyttö

Google Play ilmoittaa React Native -sovelluksen latauskooksi 42,3 Mt ja natiivin Android-sovelluksen kooksi 10,8 Mt. Noin 74 % pienempi.

Google Playn ilmoittama Android-sovelluksen latauskoko uusille asennuksille: React Native 42,3 Mt ja natiivi Jetpack Compose 10,8 Mt.

Muistinkäyttökin väheni simulaattori- ja emulaattoritesteissä, kun sovellus oli sisäänkirjautuneena aloitusruudulla. iOS:n physical footprint laski 258,9 MiB:stä 70,0 MiB:iin ja Androidin PSS 322,9 MiB:stä 232,6 MiB:iin. Mittarit ovat alustakohtaisia: kumpaakin verrataan saman alustan React Native -versioon.

Muistinkäyttö aloitusruudulla ilman vuorovaikutusta, viiden uuden prosessin mediaani Release-testikoosteilla. iOS physical footprint: React Native 258,9 MiB, SwiftUI 70,0 MiB. Android PSS: React Native 322,9 MiB, Jetpack Compose 232,6 MiB. Simulaattori ja emulaattori.

Alustaintegraatiot

Live Activities -ominaisuuksiin käyttämäni React Native -riippuvuudet olivat bugisia. Natiivina haluamani toiminta oli helppo toteuttaa: aktiviteetti pysyy näkyvissä koko treenin ajan, näyttää nykyisen liikkeen ja laskee lepoaikaa ylöspäin. Androidilla samat treenitiedot näkyvät jatkuvassa ilmoituksessa, joka voidaan näyttää Live Update -ilmoituksena sitä tukevilla laitteilla.

Myös Health Connectin integrointi Androidilla oli helpompaa. Painon synkronointi ja treenien vienti käyttävät nyt suoraan natiiveja rajapintoja. Toteutus huomioi erikseen Health Connectin saatavuuden ja tarvittavat käyttöoikeudet.

Vakaus

Ensimmäiset Sentry-luvutkin näyttävät lupaavilta:

Ensimmäiset Sentry-tilannekuvat. Kaatumattomat käyttökerrat: React Native 99,81 %, SwiftUI (iOS) 99,97 % (+0,16 prosenttiyksikköä), Jetpack Compose (Android) 99,86 % (+0,05). Käyttäjät ilman kaatumisia: React Native 96,34 %, SwiftUI (iOS) 99,50 % (+3,16 prosenttiyksikköä), Jetpack Compose (Android) 99,31 % (+2,97). Muutokset on laskettu React Nativeen verrattuna.

Ero näkyy selvemmin ilman kaatumisia selvinneiden käyttäjien osuudessa kuin käyttökerroissa. Samalla ihmisellä voi olla paljon onnistuneita käyttökertoja ja silti yksi kaatuminen. Nämä ovat ensimmäisiä lukuja Sentrystä. Seuraan niitä, kun natiiviversiot saavat lisää käyttäjiä.

Ensimmäiset pari viikkoa näyttävät lupaavilta myös RevenueCatissa: yhä useampi kokeilee Prota.

Mikä teki migraation mahdolliseksi

Minulla oli jo toimiva sovellus, backend, ulkoasu ja tekstit mallina. Varsinainen iOS-toteutus syntyi noin neljässä päivässä ja Android-toteutus noin kahdessa. Olin tehnyt alustavan kokeilun jo aiemmin. Julkaisun valmistelu ja korjaukset jatkuivat tämän jälkeen.

Muutama päivä tarkoittaa siis varsinaista toteutusta. Arkkitehtuuri, speksit, käyttöliittymän hiominen, alustaintegraatiot ja sovelluskauppojen valmistelut vaativat silti työtä. Backendin piti myös tukea käyttäjien puhelimiin jo asennettuja React Native -versioita.

Hyödyllisintä oli mielestäni antaa agenteille keinot tarkistaa työnsä. Ne pystyivät toteuttamaan ominaisuuden, ajamaan tarkistukset, katsomaan tulosta sovelluksessa ja jatkamaan korjaamista. Itse pystyin keskittymään enemmän arkkitehtuuriin, tuotepäätöksiin ja käyttöliittymän laatuun.

Jos kehität sovellusta koodausagenttien kanssa, tähän kannattaa panostaa aikaisin. Tee sovelluksesta helppo ohjata, anna agentin lukea sen tila ja tarjoa konkreettiset keinot tarkistaa lopputulos. SmartRepsissä tämä teki kahden natiivisovelluksen ylläpidosta paljon käytännöllisemmän vaihtoehdon.

Jos haluat nähdä, miltä sovellus tuntuu käytössä, kokeile SmartRepsiä. Alkuun pääsee ilmaiseksi sekä iOS:llä että Androidilla.

Haluatko kuulla lisää? Ota yhteyttä!