"오준석의 생존코딩"의 JetpackCompose 시리즈 6-9강을 수강하였습니다.
코드 설명은 코드 블록 내에 주석으로 설명했고 개념적인 부분들은 추가적으로 설명해두었습니다 ☺️
6강 (Scaffold, TextField, Button, 구조분해, SnackBar, 코루틴 스코프)
TextField(
value = "",
onValueChange = {},
)
위와 같이 작성하면 텍스트를 입력받아도 value가 빈 문자열로 설정되어있으므로 동적으로 변수값을 지정해줘야함
일반적인 방법
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//remember 사용해서 기억할 수 있게끔
val textValue = remember {
mutableStateOf("텍스트를 입력하세요")
}
Column(
//화면에 꽉 채움
modifier = Modifier.fillMaxSize(),
//가운데 정렬
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//텍스트 필드에 밸류 값 저장, onvaluechange 내용 변하는 부분 작성
TextField(
//여기 value = "" 이렇게 하면 아무리 키보드 입력해도 value 값이 변하지 않기에 입력 안 됨
//동적으로 value값 변화 시켜야 함, value 내부의 값 변수 같은 형태로
value = textValue.value,
onValueChange = {
//it은 사용자가 입력한 새로운 텍스트
textValue.value = it
},
)
//onclick으로 구현
Button(onClick = {}) {
//버튼 안 글자 작성
Text("클릭!")
}
}
}
}
}
@Composable
fun HomeScreen(viewModel: MainViewModel = viewModel()) {
var (text, setText) = remember {
mutableStateOf("Hello World")
}
Column() {
Text("Hello World")
Button(onClick = { }) {
Text("클릭")
}
//텍스트 지우면 onvaluechange 변경하면서 계속 setText호출
TextField(value = text, onValueChange = setText)
}
Text("Hello World")
}
class MainViewModel: ViewModel() {
//mutablestateof 쓰기 읽기 가능
//state 읽기만 가능
private val _value: MutableState<String> = mutableStateOf("Hello World")
val value: State<String> = _value
}
코틀린의 구조분해 기법을 활용한 방법
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//remember 사용해서 기억할 수 있게끔
val (text, setValue) = remember {
mutableStateOf("")
}
//rememberScaffoldState -> 구버전
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) }
) { paddingValues ->
Column(
//화면에 꽉 채움
modifier = Modifier.fillMaxSize()
.padding(paddingValues),
//가운데 정렬
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
//텍스트 필드에 밸류 값 저장, onvaluechange 내용 변하는 부분 작성
TextField(
//여기 value = "" 이렇게 하면 아무리 키보드 입력해도 value 값이 변하지 않기에 입력 안 됨
//동적으로 value값 변화 시켜야 함, value 내부의 값 변수 같은 형태로
value = text,
onValueChange = setValue,
)
//onclick으로 구현
Button(onClick = {
scope.launch {
snackbarHostState.showSnackbar("Hello $text")
}
}) {
//버튼 안 글자 작성
Text("클릭!")
}
}
}
}
}
}
@Composable
fun HomeScreen(viewModel: MainViewModel = viewModel()) {
var (text, setText) = remember {
mutableStateOf("Hello World")
}
Column() {
Text("Hello World")
Button(onClick = { }) {
Text("클릭")
}
//텍스트 지우면 onvaluechange 변경하면서 계속 setText호출
TextField(value = text, onValueChange = setText)
}
Text("Hello World")
}
class MainViewModel: ViewModel() {
//mutablestateof 쓰기 읽기 가능
//state 읽기만 가능
private val _value: MutableState<String> = mutableStateOf("Hello World")
val value: State<String> = _value
}
강의영상이 구버전을 사용해서 변경 된 부분들이 있습니다
Scaffold란?
화면 레이아웃을 구성할 때 자주 사용하는 기본적인 UI 구조
- Top bar
- Bottom bar
- Content
- Snack bar
다양한 UI 요소 존재
Scaffold에서 Snack bar 사용하기
val snackbarHostState = remember { SnackbarHostState() } : 스낵바의 상태 기억 및 초기화
Scaffold( snackbarHost = { SnackbarHost(snackbarHostState) } )
SnackbarHost:
- SnackbarHost는 스낵바를 표시하는 장소
- 내부적으로 스낵바를 관리하고, 상태에 따라 스낵바를 화면에 표시
- SnackbarHost는 snackbarHostState를 통해 스낵바의 상태를 받아옴
snackbarHostState:
- snackbarHostState는 스낵바의 현재 상태를 나타내는 객체
- 스낵바가 표시될 때 snackbarHostState를 사용하여 표시할 메시지를 업데이트할 수 있음
7강 (Navigation)
Gradle에 다음 dependency 추가
implementation ("androidx.navigation:navigation-compose:2.7.1")
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "first",
) {
//화면을 표시할 내용들을 composable 함수로 감싸주고 이름 지정해줌
//startDestination = "first"라고 지정했기에 첫화면이 가장 먼저 뜸
composable("first") {
FirstScreen(navController)
}
composable("second") {
SecondScreen(navController)
}
//넘겨받을 값 중괄호로, backStackEntry로 넘어오는 값 객체로
composable("third/{value}") { backStackEntry ->
ThirdScreen(
navController = navController,
//backstackentry로 넘어옴
value = backStackEntry.arguments?.getString("value") ?: "",
)
}
}
}
}
}
@Composable
//navController 사용해서 이동 용이
fun FirstScreen(navController: NavController) {
//코틀린 구조 분해 사용
val (value , setValue) = remember {
mutableStateOf("")
}
Column(
//가운데 정렬
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "첫 화면")
Spacer(modifier = Modifier.height(16.dp))
//두번째 눌렀을 때 두번째 화면으로 이동하게끔
Button(onClick = {
navController.navigate("second")
}) {
Text("두번째")
}
Spacer(modifier = Modifier.height(16.dp))
TextField(value = value, onValueChange = setValue)
Button(onClick = {
//value 비어 있으면 이동하지 않게 처리
if (value.isNotEmpty()) {
navController.navigate("third/$value") //value 값 넘길 것임
}
}) {
Text("세번째")
}
}
}
@Composable
fun SecondScreen(navController: NavController) {
Column(
//가운데 정렬
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "두번째 화면")
Spacer(modifier = Modifier.height(16.dp))
Button(onClick = {
//뒤로 가는 동작
navController.navigateUp()
}) {
Text("뒤로 가기")
}
}
}
@Composable
fun ThirdScreen(navController: NavController, value: String) {
Column(
//가운데 정렬
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(text = "세 번째 화면")
Spacer(modifier = Modifier.height(16.dp))
//value 받아오기
Text(value)
Button(onClick = {
navController.navigateUp()
}) {
Text("뒤로 가기")
}
}
}
Navigation?
화면 간의 전환을 쉽게 관리할 수 있도록 도와주는 라이브러리
NavController
val navController = rememberNavController()
- NavController는 화면 간의 이동을 관리하는 역할
- rememberNavController()를 호출하여 NavController 인스턴스를 생성, 이를 사용하여 다른 화면으로의 이동을 제어
NavHost
NavHost( navController = navController, startDestination = "first" )
- 각 화면의 경로(이름)와 해당 경로에 대한 Composable 함수를 연결
BackStackEntry
composable("third/{value}") { backStackEntry ->
ThirdScreen(
navController = navController,
value = backStackEntry.arguments?.getString("value") ?: "",
)
}
- 네비게이션 스택에서 현재 위치한 화면에 대한 정보를 담고 있는 객체
- 인자로 받은 값을 꺼낼 수 있음
8강(ViewModel)
compose 내에서 ViewModel 사용하는 법(Gradle에 dependency 추가 x)
class MainActivity : ComponentActivity() {
//생성된 viewmodel 계속해서 재사용
private val viewModel by viewModels<MainViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
//이 부분이 변경되어야하니깐 state로 만들어줘야함
viewModel.data.value,
fontSize = 30.sp
)
Button(onClick = {
viewModel.data.value = "World"
}) {
Text("변경")
}
}
}
}
}
//viewmodel: activity, life cycle 동시에 가져가므로 remember 같은거 신경 쓰지 않아도 됨
class MainViewModel : ViewModel() {
val data = mutableStateOf("Hello")
}
Gradle에 dependency 추가한 경우
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")
class MainActivity : ComponentActivity() {
//생성된 viewmodel 계속해서 재사용
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val viewModel = viewModel<MainViewModel>()
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
//이 부분이 변경되어야하니깐 state로 만들어줘야함
viewModel.data.value,
fontSize = 30.sp
)
Button(onClick = {
viewModel.changeValue()
}) {
Text("변경")
}
}
}
}
}
//viewmodel: activity, life cycle 동시에 가져가므로 remember 같은거 신경 쓰지 않아도 됨
class MainViewModel : ViewModel() {
//외부에서 접근 못하도록 private 사용
//data로 변수 설정하면 private public 변수명 같아서 오류 발생해서 _data로 변경
private val _data = mutableStateOf("Hello")
//읽기 전용으로 state type로 공개
val data: State<String> = _data
//얘를 통해서만 수정 가능하도록
fun changeValue() {
_data.value = "World"
}
}
ViewModel?
Android에서 UI 관련 데이터를 저장하고 관리하는 클래스
ViewModel 인스턴스 생성
viewModel<MainViewModel>()
- MainActivity의 생명 주기와 연결되어, Activity가 파괴되더라도 데이터가 유지
- viewModel함수는 내부적으로 ViewModel을 관리, 재사용성을 보장
9강 (State 심화)
Gradle에 다음 dependency 추가
implementation("androidx.compose.runtime:runtime-livedata:1.7.3")
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HomeScreen()
}
}
}
//컴포저블 사용해서 화면 표시
//내용 변경 일어나면 state 활용 , state 매우 중요
//compose는 state 기반으로 동작함
@Composable
fun HomeScreen(viewModel: MainViewModel = viewModel()) {
val text1: MutableState<String> = remember {
mutableStateOf("Hello World")
}
//by 사용하면 텍스트가 스트링이 됨, 텍스트2의 값을 변경할 때
//by setter getter 활용
var text2: String by remember {
mutableStateOf("Hello World")
}
//mutablestate 안에 T 제네릭 받아서 리턴, 그리고 unit 리턴
//안에 있는 값을 텍스트로 취하고 세터 역할은 ..
//여기 있는 내용 수정하려면 setter를 수정해야됨
val (text: String, setText: (String) -> Unit) = remember {
mutableStateOf("Hello World")
}
//타입이 State
val text3: State<String> = viewModel.liveData.observeAsState("Hello")
//글자 입력할 때마다 recomposition 일어나는 중
Column {
Text("Hello world")
Button(onClick = {
text1.value = "변경"
print(text1.value)
text2 = "변경"
print(text2)
setText("변경")
//viewModel.value.value = "변경" -> 불가 읽기 전용이므로
viewModel.changeValue("변경")
}) {
Text("클릭")
}
TextField(value = text, onValueChange = setText)
}
}
class MainViewModel: ViewModel() {
//mutablestateof 쓰기 읽기 가능
//state 읽기만 가능
private val _value: MutableState<String> = mutableStateOf("Hello World")
val value: State<String> = _value
//gradle에 livedata 위한 디펜던시 넣어줘야
private val _liveData = MutableLiveData<String>()
val liveData: LiveData<String> = _liveData
fun changeValue(value: String) {
_value.value = value
}
}
state의 개념을 알아보기 위한 강의이므로 영상은 따로 첨부하지 않았습니다
state?
상태는 UI의 현재 상태를 나타내는 변수
Compose는 상태 기반 UI로 작동하므로, 상태가 변경될 때 UI가 자동으로 업데이트됨
MutableState 활용
val text1: MutableState<String> = remember {
mutableStateOf("Hello World")
}
- text1은 MutableState를 사용하여 정의된 상태 변수, "Hello World"로 초기화
- remember를 사용하여 상태 저장, 구성 요소가 다시 구성될 때 동일한 값을 유지
Delegated property 사용
var text2: String by remember {
mutableStateOf("Hello World")
}
- text2는 by 키워드를 사용하여 MutableState의 getter와 setter를 델리게이트
- text2의 값을 변경할 때 내부적으로 mutableStateOf의 값을 수정
Livedata와 viewmodel 사용
val text3: State<String> = viewModel.liveData.observeAsState("Hello")
- text3는 ViewModel에서 제공하는 LiveData를 관찰하는 상태
- observeAsState를 사용하여 UI가 LiveData의 값이 변경될 때 자동으로 업데이트되도록, 초기값: "Hello"
참고자료: https://www.youtube.com/watch?v=xszyeIWFsGc&list=PLxTmPHxRH3VV8lJq8WSlBAhmV52O2Lu7n&pp=iAQB
'Group Study (2024-2025) > Android' 카테고리의 다른 글
[Android] 카카오뱅크 클론 코딩 (0) | 2024.11.20 |
---|---|
[Android] 스타일 가이드와 디자인 패턴 (3) | 2024.11.13 |
[Android] Preferences Datastore, Room을 활용한 로컬 데이터 저장 (3) | 2024.11.03 |
[Android] Retrofit2를 활용한 서버통신 (2) | 2024.10.29 |
[Android] Android UI 구현 기초 (0) | 2024.10.08 |