Android Jetpack Architecture Components 가이드
Android Jetpack Architecture Components 가이드
Android Jetpack Architecture Components는 견고하고 테스트 가능한 앱을 쉽게 만들 수 있도록 도와주는 라이브러리 모음입니다.
ViewModel
ViewModel은 UI 관련 데이터를 저장하고 관리하며, 화면 회전과 같은 구성 변경에서도 데이터를 유지합니다.
기본 사용법
class MyViewModel : ViewModel() {
private val _userData = MutableLiveData<User>()
val userData: LiveData<User> = _userData
fun loadUser(userId: String) {
// 데이터 로드 로직
}
}
Activity/Fragment에서 ViewModel 가져오기
// Activity에서
val viewModel: MyViewModel by viewModels()
// Fragment에서 Activity의 ViewModel 공유
val sharedViewModel: SharedViewModel by activityViewModels()
Fragment 간 데이터 공유
// Fragment A와 B가 같은 Activity의 ViewModel을 공유
class FragmentA : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
}
class FragmentB : Fragment() {
private val sharedViewModel: SharedViewModel by activityViewModels()
}
ViewModel의 장점
- Activity lifecycle을 따르며, 상태 변경에 영향받지 않음
- Fragment 간 데이터 공유 용이
- 비즈니스 로직과 UI 분리
LiveData
LiveData는 lifecycle을 인식하는 observable 데이터 홀더 클래스입니다.
기본 사용법
class MyViewModel : ViewModel() {
private val _name = MutableLiveData<String>()
val name: LiveData<String> = _name
fun updateName(newName: String) {
_name.value = newName // Main thread
// 또는
_name.postValue(newName) // Background thread
}
}
Observer 등록
viewModel.name.observe(viewLifecycleOwner) { name ->
textView.text = name
}
Transformations
val userLiveData: LiveData<User> = repository.getUser()
// map 변환
val userName: LiveData<String> = userLiveData.map { user ->
"${user.firstName} ${user.lastName}"
}
// switchMap 변환 (새 LiveData 반환)
val userId = MutableLiveData<String>()
val user: LiveData<User> = userId.switchMap { id ->
repository.getUser(id)
}
MediatorLiveData
여러 LiveData 소스를 결합할 때 사용합니다.
val mediatorLiveData = MediatorLiveData<String>()
mediatorLiveData.addSource(liveData1) { value ->
mediatorLiveData.value = "Source1: $value"
}
mediatorLiveData.addSource(liveData2) { value ->
mediatorLiveData.value = "Source2: $value"
}
observeForever vs addSource 차이
observeForever: LiveData의 상태를 active로 만들어 즉시 동작 시작addSource: MediatorLiveData가 observe되지 않으면 동작하지 않음- 체인을 걸 때는 MediatorLiveData 사용 권장
Lifecycle 상태 체크
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
// Resume 상태일 때만 실행
}
Room Database
Room은 SQLite 위에 추상화 레이어를 제공하는 ORM 라이브러리입니다.
의존성 추가
dependencies {
implementation "androidx.room:room-runtime:2.6.1"
kapt "androidx.room:room-compiler:2.6.1"
implementation "androidx.room:room-ktx:2.6.1"
}
Entity 정의
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
@ColumnInfo(name = "first_name") val firstName: String,
@ColumnInfo(name = "last_name") val lastName: String
)
DAO 정의
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAll(): Flow<List<User>>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getById(userId: Int): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(user: User)
@Delete
suspend fun delete(user: User)
}
Database 클래스
@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
@Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
).build()
INSTANCE = instance
instance
}
}
}
}
Lifecycle
LifecycleObserver를 통해 lifecycle 이벤트에 반응하는 컴포넌트를 만들 수 있습니다.
LifecycleObserver 구현
class MyObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
// Resume 시 실행
}
override fun onPause(owner: LifecycleOwner) {
// Pause 시 실행
}
}
// 등록
lifecycle.addObserver(MyObserver())
Application Lifecycle 관찰
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
ProcessLifecycleOwner.get().lifecycle.addObserver(
object : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
// 앱이 포그라운드로 왔을 때
}
override fun onStop(owner: LifecycleOwner) {
// 앱이 백그라운드로 갔을 때
}
}
)
}
}
Activity Lifecycle Callbacks
registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
private var activityCount = 0
override fun onActivityStarted(activity: Activity) {
activityCount++
}
override fun onActivityStopped(activity: Activity) {
activityCount--
if (activityCount == 0) {
// 모든 Activity가 종료됨
}
}
// 나머지 콜백 구현...
})
Paging
대용량 데이터를 효율적으로 로드하기 위한 라이브러리입니다.
기본 구조
// DataSource
class UserDataSource : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
val page = params.key ?: 1
return try {
val users = api.getUsers(page, params.loadSize)
LoadResult.Page(
data = users,
prevKey = if (page == 1) null else page - 1,
nextKey = if (users.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
}
ViewModel에서 사용
class UserViewModel : ViewModel() {
val users: Flow<PagingData<User>> = Pager(
config = PagingConfig(pageSize = 20)
) {
UserDataSource()
}.flow.cachedIn(viewModelScope)
}
RecyclerView에 연결
class UserAdapter : PagingDataAdapter<User, UserViewHolder>(UserDiffCallback()) {
// 구현
}
// Fragment/Activity에서
lifecycleScope.launch {
viewModel.users.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}
테스트
ViewModel 테스트
@Test
fun `test user loading`() = runTest {
val viewModel = MyViewModel(mockRepository)
viewModel.loadUser("123")
assertEquals("Expected Name", viewModel.userData.value?.name)
}
LiveData 테스트
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
@Test
fun `test livedata update`() {
val viewModel = MyViewModel()
viewModel.updateName("Test")
assertEquals("Test", viewModel.name.value)
}
결론
Jetpack Architecture Components를 활용하면 lifecycle을 고려한 안정적인 앱을 개발할 수 있습니다. ViewModel로 UI 데이터를 관리하고, LiveData로 반응형 UI를 구현하며, Room으로 로컬 데이터를 효율적으로 저장하세요.
Comments