Skip to content
← All posts
android kotlin jetpack-compose claude-code media3 ქართული

Radio Hangi: ვებ-აპიდან native Android აპლიკაციამდე Claude Code-ით

ჩემს მთავარ რადიოს — Radio Hangi-ს — უკვე ჰქონდა ვებ-ვერსია. მუშაობდა, უკრავდა, კარგად გამოიყურებოდა. მაგრამ ვებ-აპი ვებ-აპად რჩება: ეკრანი რომ ჩაქრება, მუსიკაც ჩერდება, lock-screen-ზე კონტროლი არ გაქვს, home screen-ზე widget-ს ვერ ჩასვამ.

ამიტომ გადავწყვიტე native Android აპლიკაცია გამეკეთებინა — სუფთა Kotlin, Jetpack Compose, Media3. და მთელი ეს გზა Claude Code-თან ერთად გავიარე.

ეს პოსტი იმაზეა, თუ როგორ იშლება ერთი დიდი იდეა მართვად ნაბიჯებად — და რატომ არის კარგად დაწერილი prompt ნახევარი სამუშაო.


იდეა: ორი ეკრანი, ორი სხვადასხვა სამყარო

დასაწყისშივე ერთი რამ მქონდა გარკვევით:

  • Screen A — Radio Hangi (მთავარი): ერთი ფიქსირებული live stream. აქ მნიშვნელოვანია now-playing metadata — სიმღერის სათაური, შემსრულებელი, album art, რომელიც ავტომატურად იცვლება, და lyrics.
  • Screen B — World Radio: ათასობით სადგური Radio Browser API-დან. აქ album art-ი და lyrics არ არსებობს — მხოლოდ სადგურის favicon.

ეს განსხვავება კრიტიკული იყო. ორ ეკრანს სრულიად განსხვავებული data model აქვს, და ყველაზე ხშირი შეცდომა სწორედ მათი არევაა.


საიდუმლო: prompt, რომელიც ნამდვილად აზროვნებს

Claude Code-ს არ მივწერე “გამიკეთე რადიო-აპი”. ამის ნაცვლად დავწერე დეტალური ტექნიკური დავალება — თითქოს senior Android engineer-ს ვუსახავდი task-ს:

# ROLE
You are a senior Android engineer specializing in media-playback apps,
with deep expertise in Kotlin, Jetpack Compose, Media3 (ExoPlayer +
MediaSessionService), and Material 3 design.

# CONSTRAINTS
- 100% Kotlin + Jetpack Compose. No XML layouts.
- All network calls on Dispatchers.IO, wrapped in try/catch.
- Handle every failure gracefully: no-network, dead stream,
  missing art, missing lyrics. The app must never crash on bad data.

და ყველაზე მნიშვნელოვანი ნაწილი — milestone-ებად დაყოფა, სადაც Claude თითო ეტაპის შემდეგ ჩერდება და დასტურს მთხოვს:

  1. Project scaffold — Gradle, dependencies, ვერსიებით
  2. Architecture — MVVM, single-activity, package structure
  3. Playback core — MediaSessionService + ExoPlayer + audio focus
  4. Screen A — now-playing polling, album-art chain, lyrics
  5. Screen B + Favorites
  6. Home-screen widget (Glance)

გაკვეთილი: როცა AI-ს ეუბნები “გააკეთე ყველაფერი ერთბაშად”, იღებ ერთ უზარმაზარ, გადაუმოწმებელ კოდის ნაგავს. როცა ეუბნები “გააკეთე ნაბიჯი 1 და გაჩერდი” — იღებ კოდს, რომელსაც ნამდვილად კითხულობ და იგებ.


მუშაობის პროცესი

Android Studio, Claude Code და emulator გვერდიგვერდ — Radio Hangi-ის განვითარება

ტიპური session ასე გამოიყურებოდა: მარცხნივ — კოდის ხე და Claude Code-ის ცვლილებები (მწვანე/წითელი diff-ები terminal-ში), მარჯვნივ — ცოცხალი emulator, სადაც მაშინვე ვხედავ შედეგს.

Claude წერდა milestone-ს, მე ვუშვებდი emulator-ზე, ვუჩვენებდი რა მუშაობდა და რა — არა, და ვაგრძელებდით. ეს არ არის autocomplete — ეს დიალოგია.


Screen A — ვინილი, რომელიც ცოცხლობს

Radio Hangi-ის მთავარი ეკრანი — ვინილის ფირფიტა და now-playing

მთავარი ეკრანი ვინილის ფირფიტის სტილშია. ცენტრში — album art, რომელიც სიმღერის შეცვლისთანავე ავტომატურად ახლდება.

ყველაზე საინტერესო ნაწილი იყო album-art-ის მოძიების ჯაჭვი (fallback chain):

  1. ჯერ ვცდი art-ს თვითონ now-playing metadata-დან
  2. თუ იქ არ არის — ვეძებ გარე API-ში “შემსრულებელი + სათაური”-ით
  3. თუ არსად მოიძებნა — ვაჩვენებ bundled cover.png-ს

ეს try → fallback → fallback ლოგიკა ზუსტად ის ადგილია, სადაც აპები ხშირად crash-ობენ. ამიტომ constraint-ში პირდაპირ ეწერა: “the app must never crash on bad data”.

// album art resolution — სამ საფეხურიანი fallback
val artUrl = nowPlaying.artworkUrl
    ?: artLookup.search(nowPlaying.artist, nowPlaying.title)
    ?: R.drawable.cover  // bundled default

Lyrics — და “graceful” მარცხის ხელოვნება

Lyrics ეკრანი — სიმღერის ტექსტი

Lyrics მოდის LRCLIB-დან — შემსრულებლისა და სათაურის მიხედვით. ხშირად ტექსტი ვერ მოიძებნება, და ეს ნორმალურია — მთავარია, აპი ამ დროს არ “გატყდეს”.

ამიტომ თითოეულ network call-ს აქვს მკაფიო state:

  • მოიძებნა → ვაჩვენებ ტექსტს
  • იტვირთება → spinner
  • ვერ მოიძებნა → “No lyrics available”, და არა ცარიელი თეთრი ეკრანი

ხარისხიანი აპი იქ არ ჩანს, სადაც ყველაფერი კარგად მიდის. ის იქ ჩანს, სადაც რაღაც გაფუჭდა — და აპმა მაინც იცის რა ქნას.


Screen B — მთელი მსოფლიოს რადიო

World Radio ეკრანი — სადგურების სია, ფილტრებითა და favorites-ით

მეორე ეკრანი სრულიად სხვა ხასიათისაა — directory ათასობით სადგურით Radio Browser API-დან:

  • 🔍 ძებნა debounce-ით (500ms — არ ვტენი server-ს ყოველ ასოზე)
  • 🌍 ქვეყნის ფილტრი (მხოლოდ ის ქვეყნები, სადაც ≥5 სადგურია)
  • 🏷️ ჟანრის chip-ები — multi-select
  • Favorites — ლოკალურად შენახული, count badge-ით
  • 📄 “Load more” pagination

აქ განგებ არ არის album art ან lyrics — Radio Browser-ის სადგურებს ეს data-ი არ აქვთ. ამის constraint-ში ჩაწერა მნიშვნელოვანი იყო, თორემ AI “დაეხმარებოდა” და იქაც ალბომის ყდებს მოჰყვებოდა, სადაც არ უნდა.


რა გავაკეთეთ ჯამში

ფენატექნოლოგია
UIJetpack Compose, Material 3, Navigation Compose
PlaybackMedia3 ExoPlayer + MediaSessionService
NetworkingRetrofit, OkHttp, kotlinx.serialization
ImagesCoil 3
StoragePreferences DataStore
WidgetJetpack Glance
ენა100% Kotlin

პლუს ის, რასაც ვებ-ვერსია ვერასდროს მომცემდა:

  • 🎵 background playback — ეკრანი ჩაქრა, მუსიკა უკრავს
  • 🔒 lock-screen კონტროლი + headset/Bluetooth ღილაკები
  • 📱 home-screen widget — album art, სათაური, Play/Pause, იგივე session-ზე მიბმული

დასკვნა: AI არ წერს აპს — ის წერს კოდს, შენ კი აპს

Claude Code-მ Radio Hangi-ის ათასობით ხაზი დაწერა. მაგრამ გადაწყვეტილებები ჩემი იყო: ორი ეკრანის გამიჯვნა, fallback-ების თანმიმდევრობა, “graceful failure”-ის წესი, milestone-ების რიგი.

თუ ერთ რამეს გამოვიტან ამ პროექტიდან:

კარგი prompt არ არის ბრძანება — ის არის სპეციფიკაცია. რაც უფრო ნათლად აღწერ რა და რატომ, მით უფრო ნაკლებს ასწორებ მერე.

კოდი ღიაა — შეგიძლია ნახო GitHub-ზე.


რა მოდის შემდეგ?

  • Glance widget-ის ღრმა dive — როგორ ეკონტაქტება home-screen-ის ღილაკი იმავე MediaSession-ს
  • Media3 MediaSessionService — audio focus და notification ნაბიჯ-ნაბიჯ
  • ვებ vs native — ერთი და იგივე რადიო, ორი მიდგომა, შედარება

გამოიწერე RSS — გაგრძელება მალე.


კითხვები ან იდეები? მომწერე — სიამოვნებით ვისაუბრებ Android-სა და AI-ით განვითარებაზე.