Sitemap

Compose Recomposition

19 min readJan 3, 2025

--

Press enter or click to view image in full size
Photo by Sebastian Svenson on Unsplash

Compose 를 사용하면서 Recomposition 에 대한 고찰을 하게되었다.
Recomposition 을 직접 테스트한 과정을 기록한다.

Compose 에는 ReComposition 이라는 기능이 있다.
ReComposition은 Composable 함수의 입력 데이터 또는 호출된 상태 객체의 값이 변경될 때 발생한다.

흔한 예로 데이터가 변경되었을 때 ReComposition이 발생하여 해당 Composable 함수를 다시 Rendering 해주게 된다.

그렇다면 한가지 의문이 들었다.

상위 Composable에서 선언한 데이터가 변경된다면 하위의 모든 Composable이 ReComposition 이 되는 것인가?

결론부터 말하자면 하위의 Composable 이 받는 값이 어떤 값이냐에 따라서 ReComposition 이 발생 할 수도 있고, 발생하지 않을 수도 있다.

아래의 코드(RecompositionTest)를 보면 상위 Composable 함수 (Greeting) 과 하위 Composable 함수 (GoodMorning, GoodEvening, GoodNight) 가 있다.

상위 Composable 함수 (Greeting)에 remember로 된 morningChange, eveningChange, nightChange 변수에 boolean 값 상태를 저장하고있다.

그리고 각각의 하위 Composable 함수 (GoodMorning, GoodEvening, GoodNight)는 Text를 클릭했을 때 상위 Composable 함수에서
morningChange, eveningChange, nightChange 변수 값을 반대로 변경하도록한다.
해당 remember 로 된 변수는 각각의 Composable 에 파라미터로 넣어준다.

앱을 실행하면 아래와 같은 로그를 볼 수 있다.

//앱 최초 실행 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning inner false
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening inner false
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting night inner false
[ greeting ] com.lucky.test : Greeting last

//morning text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning inner true
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting last

//evening text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening inner true
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting last

//night text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting night inner true
[ greeting ] com.lucky.test : Greeting last

상위 Composable 은 내부의 remember 로 된 변수의 값이 변경되어서 ReComposition 이 발생하였다.
하지만 하위의 Composable 은 자신이 받고있는 변수가 변경되었을 때만 ReComposition 이 발생하고있다.

만약에 상위 Composable 함수 (Greeting) 에서 remember 로 관리하는 값이 Data class 라면 어떻게 될까?

//앱 최초 실행 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning inner Greet(isMorning=false, isEvening=false, isNight=false)
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening inner Greet(isMorning=false, isEvening=false, isNight=false)
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting night inner Greet(isMorning=false, isEvening=false, isNight=false)
[ greeting ] com.lucky.test : Greeting last

//morning text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning inner Greet(isMorning=true, isEvening=false, isNight=false)
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening inner Greet(isMorning=true, isEvening=false, isNight=false)
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting night inner Greet(isMorning=true, isEvening=false, isNight=false)
[ greeting ] com.lucky.test : Greeting last

//evening text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning inner Greet(isMorning=false, isEvening=true, isNight=false)
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening inner Greet(isMorning=false, isEvening=true, isNight=false)
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting night inner Greet(isMorning=false, isEvening=true, isNight=false)
[ greeting ] com.lucky.test : Greeting last

//night text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning inner Greet(isMorning=false, isEvening=false, isNight=true)
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening inner Greet(isMorning=false, isEvening=false, isNight=true)
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting night inner Greet(isMorning=false, isEvening=false, isNight=true)
[ greeting ] com.lucky.test : Greeting last

Greet 객체가 변경되었을때 모든 하위 Composable 에서 Recomposition 이 발생하였다.

그렇다면 Greet 객체의 변수를 각 하위 Composable 에 넘겨주면 어떻게 될까?

//morning text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning inner true
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting last

//evening text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening inner true
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting last

//night text 클릭 시 로그
[ greeting ] com.lucky.test : Greeting
[ greeting ] com.lucky.test : Greeting after
[ greeting ] com.lucky.test : Greeting morning after
[ greeting ] com.lucky.test : Greeting evening after
[ greeting ] com.lucky.test : Greeting night inner true
[ greeting ] com.lucky.test : Greeting last

결과는 처음과 같이 변경된 변수를 받는 Composable 함수만 Recomposition이 발생하였다.

이와 같은 예시들의 결과를 봤을 때 Composable 함수에 전달되는 값 또는 내부의 값이 변경 되었을 때 Recomposition 이 발생하게 된 다는 것을 알 수 있었다.

remember 로 되어있지 않다면 Recomposition 은 발생할까?

이제껏 예시들에 나온 값들은 모두 remember 로 되어있었다.
만약에 remember 를 제거하면 어떻게 될까?

//morning text 클릭 시 로그
[ greeting ] com.lucky.test : morningChange true

//evening text 클릭 시 로그
[ greeting ] com.lucky.test : eveningChange true

//night text 클릭 시 로그
[ greeting ] com.lucky.test : nightChange true

결과는 모든 Composable 에서 ReComposition 이 발생되지 않았다.
클릭했을 때 값이 변하는 것을 확인하였지만 왜 Recomposition 이 되지않았을까?

그 이유는 그 변수가 변경되었다는 것을 알지 못했기때문이다.
Compose 는 상태가 변경되었다는 것을 인지하였을 때 Recompose 를 시키는데, 위와 같은 변수는 값이 변경이 되었다고 하더라도 Compose 에서는 변경된 것을 인지할 수가 없기때문에 Recompose 할 수가 없었던 것이다.

Get parkwow13’s stories in your inbox

Join Medium for free to get updates from this writer.

그렇다면 상태가 변화되었다는 것을 감지하기위해서는 어떻게 해야할까?

remember 와 stateOf 의 조합

제일 처음에 보여주었던 remember 로 상태를 유지하고, mustableStateOf 로 상태가 변경되었다는 것을 Compose 에 알리도록한다.

[ greeting ] com.lucky.test : isGood true
[ greeting ] com.lucky.test : Greeting morning inner true
[ greeting ] com.lucky.test : Greeting2 last

inner 가 찍힌 걸로 봐서 Recomposition 이 잘 발생했다는 것을 알 수 있다.

remember 가 없거나, stateOf 가 없으면 어떻게 될까?

먼저 stateOf 가 없을 때를 보자

[ greeting ] com.lucky.test : isGood true

클릭된 로그만 있고, Recomposition 이 호출된 로그는 없다.

좀 더 자세히 이야기를 하기위해 예시를 List 로 바꿔서 보자
remember 내부에 mustableStateOf 가 아닌 mutableListOf 로 넣었다.

하위 Composable
[ greeting ] com.lucky.test : dayChange [1, 2, 3, 4]
[ greeting ] com.lucky.test : dayChange [1, 2, 3, 4, 5]

텍스트를 클릭했을 때 데이터가 변경된 것은 확인되었지만 역시나 Recomposition 은 발생하지않았다.

remember 로 상태가 유지는 되었지만 Compose 에 상태가 변경되었다는 것은 알리지못했기때문에 Recompositon 이 발생하지 않았던 것이다.

이와 같은 경우 SnapshotStateList 를 이용하면 Recomposition 을 발생시킬 수 있다.

[ greeting ] com.lucky.test : dayChange snapshot SnapshotStateList(value=[1, 2, 3, 4])@207857973
[ greeting ] com.lucky.test : dayChange snapshot SnapshotStateList(value=[1, 2, 3, 4, 5])@207857973

SnapshotStateList 를 받고있는 mutableStateListOf 를 remember 내부에 구성하였다.
이 상태로 앱을 실행하면 Recomposition 이 발생하지않는다.

SnapshotStateList 의내부의 값이 변경되었지만 daychange 변수가 받는 값은 변하지않았기때문에 상태 추적은 할 수있는 상태지만 변경이 되었다는 것은 알 수가 없다.
그래서 toList() 를 사용하여 상태가 변경되었다는 것을 알려주도록 해야한다.

SnapshotStateList 내부의 toList 의 주석은 다음과 같이 적혀있다.

Return a list containing all the elements of this list.
The list returned is immutable and returned will not change even if the content of the list is changed in the same snapshot. It also will be the same instance until the content is changed. It is not, however, guaranteed to be the same instance for the same list as adding and removing the same item from the this list might produce a different instance with the same content.
This operation is O(1) and does not involve a physically copying the list. It instead returns the underlying immutable list used internally to store the content of the list.
It is recommended to use toList when using returning the value of this list from androidx. compose. runtime. snapshotFlow.

[ greeting ] com.lucky.test : dayChange snapshot SnapshotStateList(value=[1, 2, 3, 4])@173396118
[ greeting ] com.lucky.test : Greeting days inner [1, 2, 3, 4]
[ greeting ] com.lucky.test : Greeting5 last

이렇게 toList() 를 하면 값이 계속 추가되면서 Recomposition 이 발생하는 것을 확인 할 수 있다.

이젠 remember 를 제거하였을 때를 확인해보자.
remember 는 상태의 유지가 주 목적이기때문에 Recompostion 은 문제없이 될 것이다.

[ greeting ] com.lucky.test : dayChange SnapshotStateList(value=[1, 2, 3, 4])@173396118
[ greeting ] com.lucky.test : Greeting days inner [1, 2, 3]
[ greeting ] com.lucky.test : Greeting6 last
[ greeting ] com.lucky.test : dayChange SnapshotStateList(value=[1, 2, 3, 4])@173396118
[ greeting ] com.lucky.test : Greeting days inner [1, 2, 3]
[ greeting ] com.lucky.test : Greeting6 last

위와 같이 Recomposition 은 문제가 없다.
다만, remember 로 이전 상태 값을 유지 할 수 없기때문에 매번 초기화가 되는 것을 볼 수 있다.
이와 같은 경우 외부에 stateOf 변수를 선언하여 해결 할 수 있다.

[ greeting ] com.lucky.test : dayChange SnapshotStateList(value=[1, 2, 3, 4])@14519627
[ greeting ] com.lucky.test : Greeting days inner [1, 2, 3, 4]
[ greeting ] com.lucky.test : Greeting7 last
[ greeting ] com.lucky.test : dayChange SnapshotStateList(value=[1, 2, 3, 4, 5])@14519627
[ greeting ] com.lucky.test : Greeting days inner [1, 2, 3, 4, 5]
[ greeting ] com.lucky.test : Greeting7 last

Recomposition 도 발생하면서 값도 계속 증가하는 것을 확인 할 수가 있다.
(이렇게 외부에 선언하는 경우 ViewModel 에서 관리하는 것이 좋다.)

이와 같은 예시들을 보았을 때 데이터의 상태를 기억하고, 그 상태가 변경이 되었을 때 그것을 알려준다면 Recomposition이 발생한다는 것을 알 수 있었다.
이러한 데이터의 상태변경을 효율적으로 관리해준다면 무분별한 Rendering 을 막을 수 있을 것이다.

PS.
ReComposition 을 관리하기위해서는 상태호이스팅을 효율적으로 사용하는 것도 좋은 방법이다.

추가로 현재 사용된 코드에는 remember 가 아닌 rememberSavable 가 사용되기도했는데, rememberSavable 는 기존의 Activity 에서 사용하던 savedInstanceState 와 같은 기능을 하기 때문에 객체로 값을 넣을 떄는 serializable 또는 parcelable 한 직렬화가 가능한 객체로 넣어주어야한다.

--

--