[Compose] SideEffect

KSang·2024년 4월 24일
0

TIL

목록 보기
90/101

사이드 이펙트 = 부수효과

LaunchedEffect: 컴포저블의 범위에서 정지 함수 실행
rememberCoroutineScope: 컴포지션 인식 범위를 확보해 컴포저블 외부에서 코루틴 실행 (코루틴 스코프를 만들고 유지하기 위해, 컴포지션 인식범위를 확대해서 컴포지션 외부에서 코루틴을 실행할 수 있게 해준다)
rememberUpdatedState: 값이 변경되는 경우 다시 시작되지 않아야 하는 효과에서 값 참조
DisposableEffect: 정리가 필요한 효과
SideEffect: Compose 상태를 비 Compose 코드에 게시
produceState: 비 Compose 상태를 Compose 상태로 변환
derivedStateOf: 하나 이상의 상태 객체를 다른 상태로 변환
snapshotFlow: Compose의 상태를 Flow로 변환

스낵바를 표기하는건 코루틴이 필요함, 시간의 변화에 따라 시정되는 코드들이 있음(애니메이션 사라짐 등등)
LaunchedEffect는 하나의 키가 필요함(스낵바 상태 등) 키가 바뀌지 않는 이상 같은 코루틴 스코프를 사용하려함
2가지 경우 키 캔슬
1.키로 제공되는것이 값이 바뀔때
2.런치드 이펙트가 더 이상 유효하지 않을때

rememberCoroutineScope 코루틴 스코프를 만들어서 쓸수 있게함
버튼의 onClick 같은 액션이 발생할때 스코프를 만들어서 사용가능 (컴포저블의 파라미터에 넣을시 생명주기에 맞춰서 코루틴스코프가 사라지거나함)

rememberUpdatedState 런치드 이펙트랑 사용하는 예,

@Composable
fun LandingScreen(onTimeout: () -> Unit) {
	val currentOnTimeout by rememberUpdatedState(onTimeout)
	
	LaunchedEffect(true) {
		delay(SplashWaitTimeMillis)
		currentOnTimeout()
	}
}

여기서 onTimeout 을 바로 쓰지 않는 이유는, 바로 런치드 이펙트에서 사용할시 키가 true로 되어있어
다시 리컴포지션이 되지 않고 skip이 됨, 이렇게 되면 이전에 있던 onTimeout을 사용하게 되는데, 새로운 onTimeout을 기억할 수 있게 currentOnTimeout으로 선언함
그냥 remember를 사용할시 상태를 가지고 있되 이전 값을 가지고 있을거임, 그렇기 때문에 rememberUpdatedState를 호출하며 새로운 상태를 받게함, 상태 자체는 remember상태지만 value는 새값으로 update되어있음

@Composable
fun HimeScreen(
	lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, // 라이프 사이클 스코프의 기본값
	onStart: () -> Unit,
	onStop: () -> Unit
) {
	val currentOnStart by rememberUpdatedState(onStart)
	val currentOnStop by rememberUpdatedState(onStop)

	DisposableEffect(lifecycleOwner) { // 얘 땜에 파라미터로 라이프 사이클 스코프 받음
		val observer = LifecycleEventObserver { _, event ->
			if(event == Lifecycle.Event.ON_START) {
				currentOnStart()
			} else if (event == Lifecycle.Event.ON_STOP) {
				currentOnStop()
			}
		}
		lifecycleOwner.lifecycle.addObserver(observer)

		onDispose {
			lifecycleOwner.lifecycle.removeObserver(observer)
		}
	}
}

키가 바뀌면 dispose하고 이펙트를 재시작,
컴포지션에서 이펙트가 나가도 onDispose호출
리소스 등을 관리할 때도 사용 가능

@Composable
fun rememberAnalytics(user: User): FirebaseAnalytics {
	val analytics: FirebaseAnalytics = remember {
		/* ... */
	}

	SideEffect {
		analytics.setUserProperty("userType", user.userType)
	}
	return analtics
}

사이드 이펙트는 매 리컴포지션 마다 호출됨

@Composable
fun loadNetworkImage(
	url: String,
	imageRepository: ImageRepository
): State<Result<Image>>
	return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {
		val image = imageRepository.load(url) // suspend함수임 호출해서 완료될때까지 기다림
		value = if (image == null) {
			Result.Error
		} else {
			Result.Success(image)
		}
	}
}

produceState는 코루틴을 제공
State를 리턴(State<Result>)
value값을 통해 상태를 반환

@Composable
fun TodoList(highPriorityKeywords: List<String> = listOf("Review","Unblock","Compose")) {
	val todoTasks = remember { mutableStateListOf<String>() }
	
	val highPriorityTasks by remember(highPriorityKeywords) {
		derivedStateOf { todoTasks.filter { it.containsWord(highPriorityKeywords) } }
	}

	Box(Modifier.fillMaxSize()) {
		LazyColumn {
			items(highPriorityTasks) { /* ... */ }
			items(todoTasks) { /* ... */ }
		}
	}
}

derivedStateOf는 State를 만듬
하나 이상을 상태 개체를 다른 상태로 바꿈
두번째 페이지, 세번째 페이지를 이동해도 새로운 상태가 생기는게 아니라 유지가 됨
불필요한 리컴포지션의 수를 줄일 수가 있기 때문에 사용한다.

val listState = rememberLazyListState()
LazyColumn(state = listState) {
	 // ...
}
LaunchedEffect(listState) {
	snapshotFlow { listState.firstVisibleItemIndex }
	.map { index -> index > 0 }
	.distinctUntilChanged()
	.filter { it == true }
	.collect {
		MyAnalyticsService.sendScrolledPastFirstItemEvent()
	}
}

스냅샷 플로우라 값이 안바뀌면 방출 안함

.distinctUntilChanged 맵을 호출할때 같은 값이 만들어질 가능성이 높아서 사용함

1개의 댓글

comment-user-thumbnail
2024년 9월 23일

slope game is a fast-paced, "endless runner" style game where you control a ball rolling down a never-ending slope

답글 달기