Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

습관 관리 앱: 개발 일기, 광고 페이지 성능 개선 및 UI 리팩토링

Billcorea 2025. 10. 22. 15:28
반응형

 

 

습관 관리 앱: 개발 일기, 광고 페이지 성능 개선 및 UI 리팩토링

광고페이지

 

오늘은 기존에 구현했던 쿠팡 파트너스 API를 연동한 광고 페이지의 성능을 개선하고 전반적인 코드 구조를 다듬는 작업을 진행했다. 사용자가 앱을 더 쾌적하게 사용할 수 있도록 만드는 데 초점을 맞췄다.

1. API 데이터 로컬 캐싱으로 성능 개선

기존 광고 페이지는 화면에 진입할 때마다 네트워크를 통해 쿠팡 API를 호출하는 방식이었다. 이로 인해 이미지 로딩 시간이 길어졌고, 네트워크 상태가 좋지 않으면 사용자 경험이 저하되는 문제가 있었다. 이를 해결하기 위해 Room 데이터베이스를 사용하여 API 응답을 로컬에 캐싱하는 방식으로 변경했다.

1.1. Room Entity 및 DAO 설정

먼저 API 응답 데이터 클래스인 `BestProduct`를 Room의 `@Entity`로 만들었다. 상품 URL은 고유 값이므로 `@PrimaryKey`로 지정했다.

CupangDto.kt


@Entity(tableName = "best_products")
data class BestProduct(
    @SerializedName("productName")
    val productName: String,
    @SerializedName("productPrice")
    val productPrice: Int,
    @SerializedName("isRocket")
    val isRocket: Boolean,
    @SerializedName("productImage")
    val productImage: String,
    @PrimaryKey // productUrl을 기본 키로 사용
    @SerializedName("productUrl")
    val productUrl: String
)
    

다음으로, 이 데이터를 조작할 `BestProductDao`를 만들고, 데이터베이스 클래스인 `HabitDatabase`에 새 Entity와 DAO를 등록했다.

HabitDatabase.kt


// 기존 Habit, HabitRecord에 BestProduct Entity 추가
@Database(entities = [Habit::class, HabitRecord::class, BestProduct::class], version = 1, exportSchema = false)
@TypeConverters(DateConverter::class, DaysConverter::class)
abstract class HabitDatabase : RoomDatabase() {
    abstract fun habitDao(): HabitDao
    abstract fun bestProductDao(): BestProductDao // 새로 추가
}
    

1.2. Repository 및 ViewModel 수정

`HabitRepository`에서는 API를 호출하여 데이터를 가져온 후, Room에 저장하는 `refreshBestProducts` 함수를 만들었다. UI에 데이터를 보여주기 위해서는 데이터베이스의 변경을 감지하는 `Flow`를 반환하는 `getBestProductsStream` 함수를 사용했다.

HabitRepository.kt


@Singleton
class HabitRepository @Inject constructor(
    private val bestProductDao: BestProductDao,
    private val cupangApi: CupangApi // CupangClient 대신 CupangApi 직접 주입
) {
    // UI는 이 Flow를 관찰하여 데이터를 표시
    fun getBestProductsStream(): Flow<List<BestProduct>> {
        return bestProductDao.getBestProducts()
    }

    // API에서 데이터를 가져와 DB를 갱신
    suspend fun refreshBestProducts() {
        val response = cupangApi.getBestCategories(...)
        if (response.data != null) {
            bestProductDao.deleteAllBestProducts()
            bestProductDao.insertBestProducts(response.data)
        }
    }
}
    

`MainViewModel`에서는 Repository가 제공하는 `Flow`를 `stateIn`을 사용해 `StateFlow`로 변환하여 UI 상태를 관리하도록 수정했다. 이로써 데이터베이스가 업데이트될 때마다 UI가 자동으로 갱신된다.

2. 좌충우돌 빌드 오류 해결기

Room과 Hilt 설정을 변경하면서 몇 가지 빌드 오류를 만났다. 해결 과정을 기록해두면 나중에 비슷한 문제를 만났을 때 도움이 될 것이다.

  • Dagger/MissingBinding for HabitDao: `DatabaseModule`을 새로 만들면서 기존에 다른 ViewModel들이 사용하던 `HabitDao`를 Hilt에 제공하는 코드를 빠뜨렸다. `DatabaseModule`에 `provideHabitDao` 함수를 추가하여 해결했다.
  • Migration FAILED: 데이터베이스 스키마를 변경하면서 버전 충돌 오류가 발생했다. 개발 단계에서는 기존 데이터가 중요하지 않으므로, Room 데이터베이스 빌더에 `.fallbackToDestructiveMigration()` 옵션을 추가하여 스키마가 변경될 때마다 데이터베이스를 재생성하도록 하여 간단히 해결했다.

3. UI/UX 개선 및 리팩토링

성능 개선 외에도 사용자 경험을 높이기 위한 몇 가지 UI 수정과 코드 구조 개선을 진행했다.

3.1. 광고 페이지 UI 수정

쿠팡 파트너스 정책에 따라 "소정의 수수료를 지급 받습니다" 문구를 추가하고, 각 상품을 클릭하면 해당 상품 페이지로 이동하는 기능을 구현했다.

AdProductPage.kt


// 1. 파트너스 안내 문구 추가 (하드코딩된 문자열은 strings.xml로 추출)
Text(
    text = stringResource(id = R.string.ad_product_page_notice),
    color = Color.Red,
    modifier = Modifier.padding(8.dp)
)

// 2. 각 상품 Row에 클릭 이벤트 추가
Row(
    modifier = Modifier
        .fillMaxWidth()
        .clickable { // 클릭 시 웹 브라우저 열기
            val intent = Intent(Intent.ACTION_VIEW, product.productUrl.toUri())
            context.startActivity(intent)
        }
        .padding(8.dp)
) {
    // ... 상품 정보 표시 ...
}
    

3.2. MainActivity 리팩토링

`MainActivity`에 있던 `MainScreen` 컴포저블을 `ui/MainScreen.kt` 파일로 분리했다. 이로써 `MainActivity`는 순수하게 Activity의 생명주기와 진입점 역할에만 집중하게 되어 코드 구조가 훨씬 명확해졌다.

3.3. 아이콘 변경

마지막으로, 하단 내비게이션 바의 광고 메뉴 아이콘을 기존 '정보(Info)' 아이콘에서 `ShoppingCart` 아이콘으로 변경하여 사용자가 '쇼핑' 또는 '상품' 관련 페이지임을 더 직관적으로 인지할 수 있도록 했다.


오늘 하루 동안 많은 것을 개선했다. 특히 데이터 캐싱을 통해 앱의 반응성을 높인 것이 가장 큰 성과라고 생각한다. 앞으로도 꾸준히 코드를 다듬고 사용자 경험을 개선해나가야겠다.

반응형