π‘μ°μνν ν¬μ½μ€μμ νμ΅ν λ€μν λ‘λ© μ λ΅μ λνμ¬ νμ νκ³ , μ₯λ¨μ μ λΆμνμ¬ μ 리νμμ΅λλ€!
λ°μ΄ν° λ‘λ© μ λ΅
- μλλ‘μ΄λ μ ν리μΌμ΄μ μμ λ°μ΄ν° λ‘λ© μ λ΅μ μ¬μ©μμ κ²½νμ κ°μ νκ³ μ±λ₯μ μ΅μ ννκΈ° μν΄ μ€μνκ² νμ©λ μ μμ΅λλ€.
- λ°μ΄ν°λ₯Ό ν¨μ¨μ μΌλ‘ λ‘λνκ³ κ΄λ¦¬νλ μ λ΅μ λ€νΈμν¬ μμ², λ°μ΄ν°λ² μ΄μ€ μ κ·Ό, UI λ λλ§κ³Ό κ΄λ ¨λ μμ μμ ν° μ°¨μ΄λ₯Ό λ§λ€ μ μμ΅λλ€.
μ ν©ν UX ν¨ν΄ μ ν
- λκ·λͺ¨ λͺ©λ‘μ νμ μ§ν©μ νμνκΈ° μν΄ λ€μν UX ν¨ν΄ μ€μ μ νν μ μμ΅λλ€.
- μ μ§μ λ°μ΄ν° λ‘λλ‘ λ€νΈμν¬ νΈλν½ κ°μμ μ΄κΈ°μ λΉ λ₯Έ λ‘λ© μλλ₯Ό μ΄μ μΌλ‘ μ»μ μ μμ΅λλ€.
Pagination
- μ¬μ©μκ° λ€μ, μ΄μ νμ΄μ§ λ²νΈ λ±μ λ§ν¬λ₯Ό μ¬μ©νμ¬ νμ΄μ§ κ° μ΄λνλ λ°©μμ λλ€.
- ꡬνμ΄ μ½μ§λ§ μ μ΄ λ°©μμ΄ λ€μ 볡μ‘ν μ μμΌλ©°, μ½ν μΈ κ° μ¬λ¬ νμ΄μ§κ° κ±Έμ³ λλ μ§κ² λ©λλ€.
Load More
- μ¬μ©μκ° λ²νΌμ ν΄λ¦νμ¬ μ²μμ νμλ κ²μκ²°κ³Ό μ§ν©μ νΌμΉ μ μμ΅λλ€.
- λ¨μΌ νμ΄μ§μ λͺ¨λ μ½ν μΈ κ° ν¬ν¨λμ§λ§, κ²μ κ²°κ³Όκ° λ§μ κ²½μ° μ²λ¦¬νκΈ° μ΄λ €μΈ μ μμ΅λλ€.
Infinite Scroll
- μ¬μ©μκ° νμ΄μ§μ λκΉμ§ μ€ν¬λ‘€νλ©΄ λ λ§μ 컨ν μΈ κ° λ‘λλ©λλ€.
- μ§κ΄μ μ΄κ³ μ¬μ©μ μΉνμ μ΄μ§λ§, ꡬνμ΄ μ΄λ ΅λ€λ λ¨μ μ΄ μμ΅λλ€.
μλλ‘μ΄λ λ°μ΄ν° λ‘λ© μ λ΅
- μλλ‘μ΄λ μ ν리μΌμ΄μ μμλ μ¬μ©μμ κ²½νμ κ°μ νκ³ μ±λ₯μ μ΅μ ννκΈ° μν΄ λ°μ΄ν° λ‘λ© μ λ΅μ΄ μ€μν©λλ€.
- λ€νΈμν¬ μμ², λ°μ΄ν°λ² μ΄μ€ μ κ·Ό, UI λ λλ§κ³Ό κ΄λ ¨λ μμ μμ ν° μ°¨μ΄κ° λ°μν μ μμ΅λλ€.
Lazy Loading
- μ§μ° λ‘λ© λ°©μμΌλ‘ νμν μμ κΉμ§ λ°μ΄ν°λ₯Ό λ‘λνμ§ μκ³ , νμν λ λ‘λνλ λ°©μμ λλ€.
- ViewPagerλ RecyclerViewμ²λΌ λ§μ λ°μ΄ν°λ₯Ό ν λ²μ λ€λ£¨μ§ μκ³ μΌλΆλ§ νμνλ κ²½μ°μ μ μ©ν©λλ€.
- μ±μ μ΄κΈ° λ‘λ© μκ°μ λ¨μΆνκ³ λ©λͺ¨λ¦¬ μ¬μ©λμ μ΅μ νν μ μμ΅λλ€.
- [RecyclerViewμμ μ§μ° λ‘λ©]
- μ΄κΈ° λ‘λ© μκ°μ μ€μ΄κ³ , μ μ λ©λͺ¨λ¦¬ μ¬μ©κ³Ό λ€νΈμν¬ νΈλν½μ μ€μΌ μ μμ΅λλ€.
class EndlessScrollListener(
val loadMore: () -> Unit
) : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val totalItemCount = layoutManager.itemCount
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
// μ€ν¬λ‘€μ΄ λμ λλ¬νμ λ λ°μ΄ν°λ₯Ό λ λ‘λ
if (lastVisibleItem + VISIBLE_THRESHOLD >= totalItemCount) {
loadMore()
}
}
companion object {
const val VISIBLE_THRESHOLD = 5
}
}
Pagination
- Paginationμ λ°μ΄ν°λ₯Ό μ¬λ¬ νμ΄μ§λ‘ λλμ΄ ν λ²μ μΌλΆλ§ λ‘λνλ λ°©μμ λλ€.
- λ€νΈμν¬ μμ²μ΄λ λ°μ΄ν°λ² μ΄μ€ 쿼리λ₯Ό μ΅μ νν λ μ μ©νλ©°, λλμ λ°μ΄ν°λ₯Ό λ€λ£° λ μ¬μ©ν©λλ€.
- [Retrofit + Pagination]
- μ μ λ©λͺ¨λ¦¬λ‘ ν° λ°μ΄ν°λ₯Ό λλμ΄μ λ‘λν μ μμ΅λλ€.
interface ApiService {
@GET("/items")
suspend fun getItems(
@Query("page") page: Int,
@Query("limit") limit: Int
): List<Item>
}
class Repository(private val apiService: ApiService) {
suspend fun loadItems(page: Int, limit: Int): List<Item> {
return apiService.getItems(page, limit)
}
}
Caching
- μΊμ±μ μ΄μ μ λ‘λλ λ°μ΄ν°λ₯Ό μ μ₯νμ¬ μ¬μ¬μ©νλ λ°©μμ λλ€.
- λ€νΈμν¬ μμ²μ μ€μ΄κ³ μ€νλΌμΈμμλ λ°μ΄ν°λ₯Ό μ¬μ©ν μ μλλ‘ νκΈ° μν΄ μ¬μ©λ©λλ€.
- [Room + Caching]
- μ€νλΌμΈ μνμμ λ°μ΄ν°λ₯Ό μ¬μ©ν μ μμ§λ§, λ°μ΄ν°μ μ΅μ μνλ₯Ό 보μ₯νμ§ μμ μ μμ΅λλ€.
Dao
interface ItemDao {
@Query("SELECT * FROM items")
fun getAllItems(): LiveData<List<Item>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertItems(items: List<Item>)
}
class Repository(
private val apiService: ApiService,
private val itemDao: ItemDao
) {
// λ€νΈμν¬μμ λ°μ΄ν° λ‘λ ν DBμ μ μ₯
suspend fun fetchItems() {
val items = apiService.getItems()
itemDao.insertItems(items)
}
// μΊμλ λ°μ΄ν° μ¬μ©
fun getCachedItems(): LiveData<List<Item>> {
return itemDao.getAllItems()
}
}
Preloading
- Preloadingλ 미리 λ°μ΄ν°λ₯Ό λ‘λν΄λλ λ°©μμΌλ‘, μ¬μ©μκ° λ°μ΄ν°λ₯Ό μμ²νκΈ° μ μ 미리 μ€λΉν©λλ€.
- μ¬μ©μκ° νΉμ νλ©΄μ μ κ·Όν κ²μ μμνκ³ λ°μ΄ν°λ₯Ό λ‘λνλ λ°©μμ λλ€.
- [ViewModel + Preloading]
- μ¬μ©μ κ²½νμ κ°μ ν μ μμ§λ§ μ¬μ©μκ° λ°μ΄ν°λ₯Ό μμ²νμ§ μμ μλ μλ μν©μμλ λΆνμν 리μμ€κ° λ©λλ€.
class MyViewModel(private val repository: Repository) : ViewModel() {
val items: LiveData<List<Item>> = liveData {
val data = repository.loadItems()
emit(data)
}
fun preloadData() {
viewModelScope.launch {
repository.loadItems()
}
}
}
Offline-First
- μ€νλΌμΈ μνμμλ λ°μ΄ν°λ₯Ό μ¬μ©ν μ μλλ‘ λ‘컬 λ°μ΄ν°λ² μ΄μ€λ₯Ό λ¨Όμ μ°Έμ‘°ν ν, νμ μ λ€νΈμν¬ μμ²μ ν΅ν΄ λ°μ΄ν°λ₯Ό μ λ°μ΄νΈνλ μ λ΅μ λλ€.
- [Room + Retrofit]
- μ€νλΌμΈ μνμμλ λ°μ΄ν°λ₯Ό μ 곡νμ§λ§, μΊμλ λ°μ΄ν°κ° μ€λλλ©΄ μ΅μ μνλ₯Ό 보μ₯νμ§ λͺ»ν©λλ€.
class Repository(
private val apiService: ApiService,
private val itemDao: ItemDao
) {
// μ€νλΌμΈ λ°μ΄ν°λ₯Ό μ°μ μ 곡νκ³ , μ΄ν λ€νΈμν¬μμ μ
λ°μ΄νΈ
fun getItems(): LiveData<List<Item>> {
val data = MediatorLiveData<List<Item>>()
val cachedItems = itemDao.getAllItems()
data.addSource(cachedItems) { data.value = it }
viewModelScope.launch {
val remoteItems = apiService.getItems()
itemDao.insertItems(remoteItems)
data.postValue(remoteItems)
}
return data
}
}
μ 리
- μλλ‘μ΄λμμ λ°μ΄ν° λ‘λ© μ λ΅μ μ¬μ©μμ κ²½νκ³Ό μ±λ₯ μ΅μ νμ λ§€μ° μ€μν μμμ λλ€.
- μ£Όμ΄μ§ μν©μ λ§κ² λ€μν μ λ΅μ μ¬μ©ν μ μμΌλ©°, λ°μ΄ν° λ‘λ© μ§μ°μ μ΅μννκ³ λ©λͺ¨λ¦¬μ λ°°ν°λ¦¬ μ¬μ©λμ μ΅μ νν μ μμ΅λλ€.