하루 만에 Android 앱 만들기
하루 만에 Android 앱 만들기
Google Trends를 보여주는 간단한 앱을 만들고 싶었다. “지금 뭐가 핫해?”를 빠르게 확인하는 앱.
Jetpack Compose를 처음 써봤는데, LLM(Claude)의 도움으로 생각보다 빠르게 만들 수 있었다. Compose 문법을 몰라도 “이런 UI 만들어줘”라고 하면 코드가 나온다.
목표
- Google Trends RSS 파싱
- 트렌드 키워드 목록 표시
- 관련 뉴스 표시
- 북마크 기능
- AdMob 광고
하루 안에 끝내기.
Jetpack Compose 시작
XML 레이아웃 대신 Kotlin 코드로 UI를 작성한다.
@Composable
fun TrendItem(trend: TrendItem, onClick: () -> Unit) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.clickable(onClick = onClick),
elevation = CardDefaults.cardElevation(4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = trend.keyword,
style = MaterialTheme.typography.titleMedium
)
Text(
text = trend.approxTraffic,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.secondary
)
}
}
}
선언형 UI라서 직관적이다. React 경험이 있으면 금방 적응된다.
Google Trends RSS 파싱
Google Trends는 RSS 피드를 제공한다.
class NewsApiImpl : NewsApi {
override suspend fun fetchTrends(): List<TrendItem> = withContext(Dispatchers.IO) {
val url = "https://trends.google.com/trending/rss?geo=KR"
val factory = XmlPullParserFactory.newInstance()
val parser = factory.newPullParser()
URL(url).openStream().use { stream ->
parser.setInput(stream, "UTF-8")
parseTrends(parser)
}
}
private fun parseTrends(parser: XmlPullParser): List<TrendItem> {
val items = mutableListOf<TrendItem>()
var item: TrendItem? = null
while (parser.eventType != XmlPullParser.END_DOCUMENT) {
when (parser.eventType) {
XmlPullParser.START_TAG -> {
when (parser.name) {
"item" -> item = TrendItem()
"title" -> item?.keyword = parser.nextText()
"ht:approx_traffic" -> item?.approxTraffic = parser.nextText()
}
}
XmlPullParser.END_TAG -> {
if (parser.name == "item") {
item?.let { items.add(it) }
}
}
}
parser.next()
}
return items
}
}
ht:approx_traffic에 검색량이 들어있다. “100K+” 같은 형태.
Pull-to-Refresh
Compose에서 당겨서 새로고침:
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TrendsScreen(viewModel: NewsViewModel) {
val uiState by viewModel.trends.collectAsState()
val isRefreshing by viewModel.isRefreshing.collectAsState()
val pullRefreshState = rememberPullToRefreshState()
PullToRefreshBox(
isRefreshing = isRefreshing,
onRefresh = { viewModel.refresh() },
state = pullRefreshState
) {
LazyColumn {
items(uiState.trends) { trend ->
TrendItem(trend = trend)
}
}
}
}
Material 3의 PullToRefreshBox를 사용하면 간단하다.
Horizontal Pager (탭 대신)
여러 화면을 좌우 스와이프로 전환:
@Composable
fun MainScreen(viewModel: NewsViewModel) {
val pagerState = rememberPagerState(pageCount = { 3 })
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> TrendsScreen(viewModel)
1 -> BookmarksScreen(viewModel)
2 -> SettingsScreen()
}
}
}
하단 네비게이션 바와 연동:
NavigationBar {
NavigationBarItem(
icon = { Icon(Icons.Default.Whatshot, "Trends") },
selected = pagerState.currentPage == 0,
onClick = {
coroutineScope.launch {
pagerState.animateScrollToPage(0)
}
}
)
// ...
}
Room 데이터베이스 (북마크)
북마크를 로컬에 저장:
@Entity(tableName = "bookmarks")
data class BookmarkedArticle(
@PrimaryKey val link: String,
val title: String,
val snippet: String,
val imageUrl: String?,
val source: String,
val bookmarkedAt: Long = System.currentTimeMillis()
)
@Dao
interface NewsDao {
@Query("SELECT * FROM bookmarks ORDER BY bookmarkedAt DESC")
fun getAllBookmarks(): Flow<List<BookmarkedArticle>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun bookmark(article: BookmarkedArticle)
@Delete
suspend fun removeBookmark(article: BookmarkedArticle)
}
Room + Flow로 실시간 업데이트.
AdMob 네이티브 광고
트렌드 목록 사이에 광고 삽입:
class AdManager(private val context: Context) {
private var adLoader: AdLoader? = null
init {
MobileAds.initialize(context)
}
fun loadNativeAd(onLoaded: (NativeAd) -> Unit) {
adLoader = AdLoader.Builder(context, AD_UNIT_ID)
.forNativeAd { nativeAd ->
onLoaded(nativeAd)
}
.build()
adLoader?.loadAd(AdRequest.Builder().build())
}
}
광고를 TrendItem처럼 표시:
@Composable
fun AdTrendItem(nativeAd: NativeAd) {
Card(
modifier = Modifier.fillMaxWidth().padding(8.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant
)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = nativeAd.headline ?: "",
style = MaterialTheme.typography.titleMedium
)
Text(
text = "광고",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.secondary
)
}
}
}
타임라인
| 시간 | 작업 |
|---|---|
| 01:10 | 프로젝트 생성, 기본 구조 |
| 15:44 | Compose UI, ViewModel |
| 15:47 | Material 3 테마 |
| 15:50 | Pull-to-refresh |
| 16:28 | Google Trends RSS 파싱 |
| 17:55 | Horizontal Pager |
| 21:06 | AdMob 연동 |
총 작업 시간: 약 8시간 (점심, 저녁 제외)
LLM 활용
솔직히 Jetpack Compose를 제대로 공부한 적이 없다. LLM에게 물어보면서 만들었다.
나: "Google Trends RSS를 파싱해서 LazyColumn으로 보여주는 코드 만들어줘"
LLM: (코드 생성)
나: "여기에 pull-to-refresh 추가해줘"
LLM: (코드 수정)
이런 식으로 대화하면서 코드를 완성했다. 모르는 API도 물어보면 설명해주고, 에러가 나면 붙여넣으면 고쳐준다.
LLM 시대의 개발:
- 새로운 프레임워크를 처음부터 공부할 필요 없음
- “이거 하고 싶어”라고 말하면 됨
- 문서 읽는 시간 대폭 감소
물론 LLM이 생성한 코드를 이해하고 검증하는 능력은 필요하다. 하지만 진입 장벽이 확실히 낮아졌다.
결론
Jetpack Compose + LLM으로 하루 만에 꽤 괜찮은 앱을 만들 수 있었다.
좋았던 점:
- 선언형 UI로 빠른 개발
- Material 3 컴포넌트가 잘 되어있음
- Preview로 즉시 확인 가능
- LLM이 Compose 코드를 잘 생성함
아쉬운 점:
- Compose 문서가 아직 부족 (하지만 LLM이 대신 설명해줌)
- 일부 컴포넌트는 Experimental
XML 레이아웃 시절로 돌아가고 싶지 않다. 그리고 LLM 없이 개발하던 시절로도.