코틀린-안드로이드

69일차)챌린지반 강의(Json, 비동기 프로그래밍), Compose강의(LaunchedEffect, rememberCoroutineScope, rememberUpdatedState, DisposableEffect, rememberLauncherForActivityResult)

songyooho 2024. 8. 14. 20:15

>챌린지반 강의

1. Json(JavaScript Object Notation)
1)Client - REST API server간 통신
-예전엔 HTML코드로 통신
=>문제점: 웹브라우저는 이해되는데 안드로이드같은 곳에선 알아볼 수 없음
-지금은 Json으로 통신
=>웹브라우저뿐만이 아니라 다른 OS들도 데이터를 인식하기 쉬워짐

2)구조 : Key - Value 형태
-{ } 는 Object
-[ ] 는 Array

3)convert에 사용할 dataclass
-"reponse" : { } => val Reponse: Body //오브젝트는 해당 부분을 데이터 클래스로
@최상위에 { }로 시작하면 해당 오브젝트에 대응되는 data class를 만들어야 한다.
-"List" : [ ] => val List: List<~> // 리스트는 리스트 형태로
-type: nullable인지 nonNull 인지 알아야 한다.

2. 비동기 프로그래밍
1)배터리 절약: 실행시간을 줄이고 HTTP 연결을 줄이는것
=>비동기 프로그래밍을 하면 실행시간이 줄어들음

2)async
-val lists = listOf( async{foo1()}, async{ foo2()}, ...)
=>val data = lists.awaitAll()하면 한번에 처리 가능

-async 블록의 반환값: 마지막 라인으로 해도 되지만 return@async로 해도 됨
=>async{ return@async ~ }

 

 

 

>Compose강의

1. 사전 정리내용

-LaunchedEffect
1)구조: LaunchEffect(key) //한번만 실행시키고 싶으면 key에 Unit
2)작동방식: composable이 처음 구성될때 실행되며 key값이 변경될 때마다 다시 실행됨
=>Recomposition이 일어나도 key값이 그대로면 재실행되지 않음
3)특징: 내부에 코루틴을 사용할 수 있음
4)주요 사용 예시
-초기 데이터 가져오기: 화면 처음구성시 데이터 불러오거나 초기화
-비동기 작업: 네트워크 요청, 데이터베이스 호출=>근데 이건 뷰모델이나 도메인에서
=>왜냐하면 composable에선 UI작업을 해야하지 다른 작업을 하면 안됨.
-애니메이션 시작
@예시
var pulseRateMs by remember { mutableStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}
5)유의점: LaunchedEffect는 composable함수이므로 Composable함수 내에서만 사용 가능

-rememberCoroutineScope
1)사용: 
-컴포저블 외부에 있으면서 컴포지션 종료후 자동으로 취소되도록 범위가 지정된 코루틴 실행
-코루틴의 수명주기를 수동으로 관리할시
=>사용자 이벤트 발생시 애니메이션 취소해야 하는 경우
2)특징: 
-수명주기가 컴포저블의 라이프 사이클과 일치
=>컴포저블이 화면에서 제거되면 코루틴도 취소
-recomposition에 영향을 받지 않음: recomposition이 발생해도 스코프가 유지되어 중복실행이나 리소스낭비 방지
3)주요 사용
-사용자 상호작용처리: 버튼 클릭. 스크롤등 상호작용에 따른 비동기 작업 처리시
@예시
@Composable
fun MoviesScreen(snackbarHostState: SnackbarHostState) {

    // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        }
    ) { contentPadding ->
        Column(Modifier.padding(contentPadding)) {
            Button(
                onClick = {
                    // Create a new coroutine in the event handler to show a snackbar
                    scope.launch {
                        snackbarHostState.showSnackbar("Something happened!")
                    }
                }
            ) {
                Text("Press me")
            }
        }
    }
}


-rememberUpdatedState
1)사용: 코루틴, 타이머, 리스터와 같은 비동기 작업이나 오래 실행되는 작업에서 Recomposition이 일어나더라도 최신 상태를 안전하게 참조하기 위해 사용하는 것
2)예시
-문제 발생 상황: 비동기 작업에서 이전 상태를 참조하는 경우
@Composable
fun Timer(onTimeout: () -> Unit, timeoutDuration: Long) {
    LaunchedEffect(timeoutDuration) {
        delay(timeoutDuration)
        onTimeout() // 이 시점에서 onTimeout이 오래된 참조일 수 있음
    }

    Text("Waiting for $timeoutDuration milliseconds...")
}
=>onTimeout이 바뀌어 리컴포지션이 일어나더라도 LaunchedEffect는 키값의 변화가 아니면 영향을 받지 않으므로 나중에 실행할때 오래된 onTimeout객체를 참조하고 있는 문제가 발생

-해결
@Composable
fun Timer(onTimeout: () -> Unit, timeoutDuration: Long) {
    // 최신 onTimeout 콜백 참조를 기억
    val updatedOnTimeout by rememberUpdatedState(onTimeout)

    LaunchedEffect(timeoutDuration) {
        delay(timeoutDuration)
        updatedOnTimeout() // 항상 최신의 onTimeout을 호출
    }

    Text("Waiting for $timeoutDuration milliseconds...")
}
=>rememberUpdatedState()을 사용하면 해당 값이 최신객체를 참조하고 있도록 해줌



-DisposableEffect: Composable의 lifecycle에 맞춰 리소스 정리가 필요할때 사용
1)구조:
DisposableEffect(key){
.
.
.
onDispose{ ~ }
}
2)작동: 
-DisposableEffect블록: 처음 Composition시와 key값이 바뀔때마다 실행됨
-onDispose블록: key값이 바뀔때와 Composable종료(컴포저블이 화면에서 제거되거나 컴포지션 종료)시 실행됨
3)사용
-컴포지션 시작시 리소스 초기화하고 컴포지션 종료시 정리작업이 필요할때
-key값이 변경될때마다 기존 리소스를 정리하고 새 리소스 초기화가 필요할때
@여기서 리소스에는 리스너 같은것도 포함

4)예시
@Composable
fun MyComposable(lifecycleOwner: LifecycleOwner) {
    // DisposableEffect를 사용하여 생명주기 관찰자 등록
    DisposableEffect(lifecycleOwner) {
        val observer = object : LifecycleObserver {
            @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
            fun onResume() {
                println("ON_RESUME 이벤트 발생")
            }

            @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
            fun onPause() {
                println("ON_PAUSE 이벤트 발생")
            }
        }

        // 생명주기 관찰자 등록
        lifecycleOwner.lifecycle.addObserver(observer)

        // 컴포지션이 종료되거나 key가 변경될 때 호출되어 관찰자 해제
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
            println("관찰자 해제됨")
        }
    }

    // UI 코드
    Text("Lifecycle aware component")
}

 

 

2. 강의 정리

-단발성 이벤트 전달은 sharedFlow로 주로 함
-컴포서블의 라이프사이클오너 받는법: LocalLifecycleOwner.current
=>라이프 라이클의 특정 시점마다 받복 실행: 

lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.~){
    launch{ ~ }
}
=>코루틴 스코프내에서 사용가능

-컴포스에선 AlertDialog로 다이얼로그 띄움

-ActivityforResult은 rememberLauncherForActivityResult

-find{} 조건에 맞는게 있으면 원소를 찾고 아니면 null