AUTOR
Ondřej Bartoněk
5
.
9
.
2023
|

Z XML do Jetpack Compose: přínosy, výzvy a kompromisy

Jednu z našich Androidích aplikací jsme začátkem tohoto roku začali přepisovat do Jetpack Compose. Proč to děláme a přiděláváme si zbytečně práci? Chceme být sexy? Jít s programovacími trendy? Mít jako firma lepší pozici při hledání nových kandidátů? Anebo alespoň - když se partnerka doma zmíní, že si zase koupila nové boty, chceme mít možnost opáčit: “Lásko, to je krásné, já si zase updatoval paging libku a vyzkoušel novou implementaci map v Compose.”?

To asi nebudou ty pravé důvody, byť na nich něco bude. V tomto článku bych rád situaci přepsání aplikace do nového UI frameworku více rozebral a podíval se na tento problém z více úhlů za využití příkladu jedné z aplikací, kde přepis aktuálně řešíme.

Proč přepisovat mobilní aplikace

Jako vývojáři moc dobře víme, v jak dynamickém prostředí se pohybujeme. To už ale nemusí být tak úplně zřejmé pro ty, kteří projekty řídí. Jedním z našich úkolů (tedy spíše asi těch seniornějších z nás) je proto tuto skutečnost pravidelně, trpělivě a postupně klientovi dávkovat. Jak v zájmu životnosti projektu, tak našeho duševního zdraví a tím pádem rychlosti vývoje.

Nové technologie totiž přicházejí kolikrát mnohem rychleji než módní trendy. To by samo o sobě nebylo zas až tak kritické, ale bohužel tím staré technologie (starší než ty zbrusu nové) rychle přestávají být od autorů udržované a aktualizované. O kompatibilitě s ostatními knihovnami a bezpečnostních rizicích ani nemluvě. Na vývojáře je tak kladen obrovský tlak - balancovat vývoj nových featur a snažit se aktualizovat již použité knihovny tak, aby nevznikal přílišný technický dluh.

Ti z vás, kteří četli Clean Code od strýčka Boba si určitě vzpomenou na křivku technického dluhu. Pro ty kteří knihu nečetli velice krátce: s rostoucím technickým dluhem rostou náklady na změnu exponenciálně. Přepsat tedy XML soubory do Compose je prostě “jen” další odstraňování technického dluhu. Jak si ale ukážeme, není to úplně triviální záležitost, a to z mnoha důvodů.

S jakou appkou pracujeme

V našem případě řešíme středně až více komplexní mobilní aplikaci s mnoha zajímavými funkcemi (pagingové seznamy, volání platebních bran, volání bankovní identity, volání jiných aplikací, deeplinky), API endpointy a s dost přiohnutými UI prvky. Je tedy potřeba si uvědomit, že přepis celé aplikace není (a nebude) otázkou jednoho merge requestu, ale během na dlouhou trať. Z důvodu omezených kapacit je postupný přepis navíc jediné řešení, přijatelné jak pro klienta, tak pro vývojáře. A samozřejmě - je potřeba počkat na vhodný moment…

Exekuce krok za krokem

Interně jsme se proto předem domluvili na tom, že v první fázi budeme zcela nové features psát v Compose a staré necháme tak, jak jsou. Ze začátku (než opadne rozvířený prach po prvotních Compose záškytech) je alespoň budeme čistit od “balastu” utility metod a mazat, co půjde v rámci kontextuálních změn. Až poté budeme pomýšlet na samotnou extrakci a přepsání starších částí kódu do Compose.

Jakmile jsme dostali zelenou ke tvorbě zbrusu nové feature, mohli jsme začít:

  1. Jelikož naše aplikace byla ještě před nedávnem de-facto monolit, využíváme této situace a zároveň s novým UI přístupem zakládáme library submoduly: pro čisté Compose komponenty, také definici barev, fontů atp.; pro staré “XML UI” - extenze Views, Listenerů atd. a co dál šlo, jsme přesunuli do společného submodulu určeného hlavně pro resources a drawable. V library tak máme nyní celkem tři nové submoduly pro potřeby UI.
  2. Dle smluvených architektonických přístupů vytváříme feature modul a do něj (opět submodul) sypeme novou featurku.
  3. Konfigurujeme gradle. Vytváříme např. sadu vlastních pluginů, díky kterým budeme ve feature a library modulech sdílet společnou konfiguraci (např. buildFeatures.compose = true, namespace, a SDK konfigurace).
  4. Řešíme navigaci do nových Compose featur, ze které se stává jeden z největších kompromisů celého přepisu. Nakonec se ji rozhodujeme ponechat tak, jak je (hlavně z důvodů obav o stávající funkcionalitu). To ale znamená, že nové Compose screeny musíme zawrapovat do Fragmentu a androidx.compose.runtime.Composable. Naštěstí to jde bez problémů. Jedná se nicméně pouze o dočasné řešení, které budeme chtít do budoucna odstranit.
  5. Vlastní tvorba Compose komponentů v dedikovaném modulu je již třešnička na dortu a od této chvíle naše každodenní radost.

Další kompromisy přechodu do Compose

Navigace do nových Compose features nebyla jediným kompromisním dočasným řešením, ke kterému jsme přistoupili. Namátkou bych rád uvedl pár dalších konkrétních příkladů, se kterými jsme se museli naučit žít:

  1. Wrapování tzv. BottomSheet dialogů - z Compose voláme Fragment (androidx.appcompat.app.AppCompatDialogFragment) a z něj opět Compose. Důvodem je požadavek, aby dialog překrýval i spodní navigační toolbar, a v té době ještě není ModalBottomSheet z androidx.compose.material3.
  2. Debugging Compose mapy (com.google.maps.android.compose.GoogleMap) pouze z release buildType - po několika hodinách hledání, proč se animace posunu mapy na zařízení vykresluje jako fotoalbum, zjišťujeme, že máme buď na zařízení vypnout StrictMode anebo poslat do zařízení již zmíněný release.
  3. Králičí nory v podobě refaktoringu utility metod - když už se rozhodneme, že součástí změn bude i menší extrakce a čistka monolitního modulu (kvůli nějaké utilitě, kterou zrovna v Compose modulu rádi využijeme), nikdy dopředu nelze vědět, jak hluboko to až povede.

Závěrem

Obecná kuchařka pro to, jak přistupovat k přepisu aplikace do zcela nového UI frameworku nejspíš neexistuje. Jelikož programování je činnost kreativní, každá aplikace může být psána jinak, např. v jiných architekturách, v jiných programovacích jazycích, používat jiné knihovny třetích stran, které ale mohou poskytovat stejnou funkcionalitu. A naopak, už jenom odlišné verze totožných knihoven stačí k tomu, abychom se na přepis mohli dívat jinak.

Co si tedy z tohoto článku odnést?

  • Vytvořte plán přepisu, který odráží realitu vašeho projektu (přístup klienta k technickému dluhu, kapacity, životnost projektu…)
  • Pokud nemáte k dispozici časovou dotaci pro kompletní přepis codebase, sžijte se s faktem, že některé UI prvky/proměnné budou duplikované (i když třeba jen dočasně).
  • Dokumentujte, dokumentuje a znovu dokumentujte - a dokumentaci udržujte aktuální. Proč je něco nějak a něco zase jinak? Co dělat v případě, že chceme…? (pozn. My tento problém vyřešili JIRA tickety pro sledování toho, co je/není hotovo. Přímo v codebase jsme pak např. aktualizovali README a CONTRIBUTING soubory tak, aby pro nás bylo jednodušší dodržovat nastavené standardy).
  • Akceptujte, že přepis bude trvat dlouho a některé staré věci se možná nikdy nepřepíší.

Přepis zkrátka není jednoduchá věc a obvykle má od ideálního scénáře poměrně daleko.  I přesto doufám, že podobné eskapády u vás na pořadech dnů opravdu jsou - znamená to, že vaše codebase je zdravá a klient vaši snahu o její zachování v dobrém stavu chápe a podporuje. A to je podle mě zdravý byznys.

Přečti si taky