Jetpack Compose의 안티패턴

milkbottle·2024년 6월 2일
0

Jetpack Compose의 안티패턴

안티패턴은 자주 사용하지 않거나, 권장되지 않는 방법들을 의미한다.

예를 들면, 다음과 같은 코드는 안티패턴이다.

Jetpack Compose에도 많은 안티패턴이 존재하는데 이해하기 쉽게 설명해보겠다.

context를 @Composable 외부에서 사용

context는 기존 xml 방식의 Android에서는 어디서든지 사용가능하다.

Fragment, Activity가 하나의 class인데 이 객체를 this로 참조하면 context를 반환한다.

하지만, Jetpack Compose에서는 Single Activity이며 No Fragment이다.

그리고 Activity조차도 xml 방식에서는 AppCompatActivity지만, Jetpack Compose에서는 ComponentActivity로 다른 개념을 사용한다.

기존 방식은 Fragment, Activity같은 위젯 내부에서 context를 호출하기 때문에 Jetpack Compose도 내부적으로는 @Composable을 Fragment, Activity들을 응용해서 만든다.

즉, context를 사용하려면 @Composable 안에서만 사용해야하는 것이다.

만약 토스트 메시지, SharedPreferences와 같은 context를 사용하는 함수를 쓰려면, @Composable 안에서 사용해야한다.

예시: 뷰모델에서 토스트 메시지를 전송

뷰모델에서 토스트 메시지를 보내려면, 뷰모델에서 context를 사용해야한다.

토스트 메시지를 보내는 함수는 context가 필요하기 때문이다.

하지만, viewmodel은 @Compsoable이 아니다.

이럴 땐 여러가지 방법이 있다.

  1. ViewModel이 아닌, AndroidViewModel을 사용한다.
  2. State를 따로 생성해 @Compsable에서 이를 감지하여, 토스트 메시지를 뿌린다.

여기서는 1은 안티패턴이다. 이유는 밑에서 설명하겠다.

실제로는 2번을 자주 사용한다.

// ViewModel
class MyViewModel : ViewModel() {
    private val _toastMessage = MutableLiveData<String>()
    val toastMessage: LiveData<String> = _toastMessage

    fun showToast(message: String) {
        _toastMessage.value = message
    }
}

// Composable
@Composable
fun MyComposable(viewModel: MyViewModel) {
    val toastMessage by viewModel.toastMessage.observeAsState()
    
    // UI 내부에서 toastMessage 값 변경 감지
    toastMessage?.let { message ->
        Toast.makeText(ContextAmbient.current, message, Toast.LENGTH_SHORT).show()
    }

    // 이후에 필요한 UI 구성 요소들을 정의
}

이런식으로 뷰모델에서 message값이 null이다가 값이 지정되면 이벤트를 감지해 메시지를 보내는 것이다.

AndroidViewModel의 사용

아까 토스트 메시지 예시에서 설명했지만 이방법은 잘쓰이지 않는다.

사실 AndroidViewModel을 사용하는 것은 안티패턴급은 아니지만, 메모리관리나 라이프사이클관리의 소요가 있기에 거의 피해야하는 패턴이다.

AndroidViewModel은 ViewModel을 확장한 개념으로 내부에서 context를 사용할 수 있다.

이는 AndroidViewModel이 Android 프레임워크의 컴포넌트에 바인딩되어 있을 때 안전하게 사용될 수 있음을 의미한다.

따라서 AndroidViewModel을 사용할 때에는 Android 프레임워크의 생명주기와 함께 사용하는 것이 중요하다.

하지만, Jetpack Compose는 Android 생명주기를 다룰 수가 없다.

그래서 AndroidVieModel을 잘 사용하지 않는 것이다.

@Composable을 @Composable 이 아닌 곳에서 호출

이것은 애초에 컴파일러 단에서 오류로 잡아준다.

하지만, 코드를 쓰다보면 어라 왜 이렇게 빨간 줄이 많이 뜨지? 하며 이유를 모를때가 있다.

예시: LaunchedEffect, DisposableEffect 에서 @Composable 호출

@Composable
fun TextComposable() {
  LaunchedEffect(Unit) {
      val text = stringResource(~~~)
  }
}

얼핏 보면 글자값을 가져와 저장하는 것처럼 보이지만, stringResource는 @Composable 함수이다.

이럴 때는 context를 불러와 이를 활용해 기존 xml 방식에서 사용하던 context.getString()을 사용한다.

@Composable
fun TextComposable() {
  val context = LocalContext.current
  LaunchedEffect(Unit) {
      val text = context.getString(~~~)
  }
}

remember의 제대로되지않은 이해

remember은 @Composable 함수로, 리컴포지션이 되어도 값을 유지하는 성질을 띈다.

그래서 @Composable 내부에서 한번만 실행되도 될만한 코드를 remember에 감싸줘야한다.

@Composable
fun Counter() {
    val count = remember {
        Log.d("test", "hi")
        mutableStateOf(0)
    }
    Button(onClick = { count.value++ }) {
        Text("Count: ${count.value}")
    }
}

이 코드를 실행해서 버튼을 눌러보면, log는 한번만 찍히게 된다.

mutableStateOf는 값이 변하는 것을 감지하는 변수를 만들어라는 함수이다.

리컴포지션이 될때마다 계속 변수를 만들어 0을 기본값으로 할당하면 0만 보일 것이다.

그래서 remember을 활용해 최초 1회만 실행해서 저장하도록 하는 것이다.

remember을 var에 저장

@Composable
fun MyComposable() {
    val rememberedValue = remember { /* some value */ }
    // 잘못된 사용: 변수에 값을 다시 할당할 때마다 새로운 상태가 생성된다.
    rememberedValue = newValue
}

0개의 댓글