반응형 분류 전체보기379 Kotlin Escape 에 대한 정리 # RawString Escaped String Literal 오늘은 Kotlin 의 String Literal 에서 사용되는 Escape 규칙에 대해서 정리해 보겠습니다. 1. Escape Kotlin String Literal 에서 Escape는 "\" 를 이용해주면 되는데요. 원래는 그냥 사용하면 제대로 전달이 안되지만, 아래와 같이 사용해주면 그대로 전달될 수 있습니다. 특히나 따옴표(Single Quote)나 쌍따옴표(Double Qoute) 같은 경우 꼭 알아두어야 합니다. 기호 의미 \" Double quote \' Single quote \\ Backslash \n Newline \r Carriage return \t Tab 2023. 4. 30. Android Default Font 지정하는 방법 정리 # Theme Jetpack Compose 요즘 개발하는 앱들은 Custom 폰트들을 적용하는 경우가 많은데요. Android 개발하면서 매번 폰트를 지정하는 일은 매우 비효율적인 일입니다. 오늘은 Android 의 Jetpack Compose 에서, Default font를 지정하는 방법에 대해서 정리해 보도록 하겠습니다. 1. Custom 폰트 저장 가장 먼저 default 로 지정할 font 를, font 폴더에 저장해 주어야 합니다. 먼저, 아래와 같이 New> resource directory를 선택해서 폰트를 넣을 디렉토리를 선택해 주구요. 2023. 4. 30. Kotlin GroupBy 구현과 정리 # List 그룹핑 오늘은 Kotlin의 GroupBy 에 대해서 정리해 보겠습니다. 1. GroupBy API 우선 해당 API의 코드를 가볍게 보고 가도록 하겠습니다. 아래에서 보듯이 GroupBy는 Iterable 타입의 collection 에서, 전달되는 특정한 기준을 Map의 Key값으로 해서, 그에 해당하는 데이터들을 List로 묶어서 Value에 넣어줍니다. 2023. 4. 28. getOrNull 과 getOrElse 에 대한 정리 # List Kotlin Kotlin에는 편리한 함수들이 정말 많은데요. 오늘은 Kotlin 의 getOrNull 과 getOrElse 에 대해서 정리해 보도록 하겠습니다. 1. getOrNull () 과 getOrElse() 1-1. getOrNull 주어진 Index에 대해서 엘리먼트가 있으면 반환해주고, 해당 index가 범위내에 없으면 null 을 반환해 주는 Kotlin Collections 의 list 입니다. 2023. 4. 28. IconButton Ripple Effect 구현 # Jetpack Compose 기존 XML 의 ImageButton 유아이에 대응되는 것이 바로 IconButton 인데요. 오늘은 이 버튼에 Ripple Effect주는 방법에 대해서 정리해 보도록 하겠습니다. 1. IconButton 에 Ripple Effect 주기 1-1. Indication XML 에서는 ImageButton으로 구현하였던 것들은 대부분 IconButton 으로 변환할 수 있는데요. 먼저 코드를 보겠습니다. 핵심적인 부분이 바로 Modifier.Indication() 부분인데요. Visual Effect 를 그려주는 부분이 됩니다. IconButton( onClick = {}, modifier = Modifier .size(40.dp) .indication(interactionSource, ripple) ){.. 2023. 4. 28. SharedPreference 로 간단한 데이터 저장하기 # Android 안드로이드 개발을 하면서 간단한 key-value 형태의 값들을 앱안에 저장할 때가 있는데요. 예를 들면, 앱의 글자 크기설정, 알람 온오프 같은 값입니다. 그런데, 이럴때마다 DB를 사용하는 것은 너무나 무거운 일인데요. SharedPreference를 사용하면 좀더 가볍고, 쉽게 해결할 수 있습니다. 1. SharedPreference 는? SharedPreference는 비교작 작은 크기의 키-밸류값을 읽고 쓸수 있도록 Android Framework에서 제공해주는 기능입니다. 위에서 애기한것처럼, 설정 값등을 저장 하는데 많이 사용 하구요. 경우에 따라서 private하게 사용하거나, share될 수 있도록 할 수 있습니다. 2. SharedPreference 초기화 먼저 SharedPrefere.. 2023. 4. 27. 다른 Origin 에서 들어오는 route 에 대한 처리 방법 # Jetpack Compose Navigation 오늘은 각각 다른 origin(출발지)에서 공유되는 Screen 으로 route가 들어오는 경우, 그것의 처리 방법에 대해서 정리해 보도록 하겠습니다. 1. 큰 그림 각각 다른 Origin에서 들어오는 경우의 생각해야 할 문제점은, 다른 UI에서 각각 다른 데이터를 들고 들어온다는 것 입니다. 이것은 origin 별로 다르게 데이터를 처리해서 보여주어야 한다는 것을 의미합니다. 이 문제를 해결하기 위한 방법은 여러가지이겠지만, 다음과 같은 방법이 있을 수 있겠네요. 우선은 isFromXXOrigin 이라는 boolean 을 route의 argument 로 받아서, 그것을 보고 각기 다르게 처리하는 방식이구요. 다른 하나는 Sealed Class를 사용해서 route를 구분해서 받는 것 입니다. isFro.. 2023. 4. 27. Kotlin Pair 와 Map 함수 이용해서 데이터 가공하기 오늘은 Kotlin 에서 Pair()와 Map() 함수를 이용하는 방법에 대해서 정리해 보겠습니다. 1. Pair 데이터 먼저 아래와 같은 Pair()객체를 list 에 여러개 있다고 가정해 보겠습니다. val testList = listOf(Pair("one", 1), Pair("two", 2), Pair("three", 3)) 참고로 to 키워드를 사용하면 Pair()생성자 형태를 사용하지 않고도 Pair 객체를 만들수 있습니다. Kotlin에 있는 infix함수라는 개념인데, 보통 사용하는 점(.)이나 괄호() 를 사용하지 않고 함수를 호출하게 해줍니다. val testList = listOf("one" to 1, "two" to 2, "three" to 3) 2023. 4. 26. TopAppBar 공유하는 방법 정리 # Jetpack Compose 오늘은 Jetpack Compose UI에서 사용하는, TopAppBar를 여러Screen에서 공유하는 방법에 대해서 정리해 보겠습니다. 1. TopAppBar 여러 Screen에서, 상단의 TopAppBar를 공유해야 한다면, Composable Function 을 작성한 후에, Scaffold에 전달해 주면 됩니다. 먼저, 아래에서는 SharedTopAppBar라는 Composable 함수를 만들었습니다. @Composable fun SharedTopAppBar( title: String, onMenuItemClick: (MenuItem) -> Unit, menuItems: List ) { TopAppBar( title = { Text(text = title) }, actions = { menuIte.. 2023. 4. 25. Room DB 에서 검색 구현하기 # Like ExactMatch SQL 오늘은 RoomDB에서 검색하기를 구현하는 방법에 대해서 정리해 보겠습니다. entity, dao 등 RoomDB에 관한 기본적인 내용은 아래 글을 참조해 주세요. >> Room DB 사용방법 총정리 # Android SQLite 1. 검색을 위해 준비한 데이터 검색을 구현하기 전에 먼저 데이터가 저장되어 있어야 하는데요. 아래와 같이 아주 간단한 데이터가 있다고 가정해 보겠습니다. 표에는 안적혀 있으나, Database명은 "student" 로 하였습니다. studentId Name 1 Aileen lee 2 Eyenie park 3 Ive Kim 2. 검색을 위한 DAO 작성 2-1. 단순한 like 검색 위의 데이터에서 이름으로 학생데이터를 검색하기 위해서는 아래와 같이 Dao를 작성해 주어야 합니.. 2023. 4. 24. mutableStateOf 와 MutableStateFlow 비교 총정리 # collectAsState Jetpack Compose UI의 중심에 있는 Concept가 State 인데요. mutableStateOf 와 MutableStateFlow를 이용하면, mutable (가변)의 State 값을, 관찰해서 값의 변화에 따라 UI 나 혹은 다른 로직의 변형을 줄 수 있습니다. 오늘은 이 둘의 차이에 관해서 정리해 보겠습니다. 1. mutableStateOf mutableStateOf에 관해서는 공식문서에 자세히 설명이 되어있는데요. 관찰될 수 있는 MutableState를 생성해주는데, 이 타입으로 된 값이 변경될 때마다, 컴포저블이 다시 recompose 되도록 한다고 되어있습니다. 즉, 값에 따라서 Composable UI의 변경을 주어야 할 때 사용하기에 적합합니다. 사용방법은 아래와 같은데요. .. 2023. 4. 23. VisualStudio Code 자주쓰는 맥 단축키 정리 # VSCode 오늘은 Visualb Studio Code 에서 자주 사용되는 단축키에 대해서 정리해 보겠습니다. 1. Rename Symbol 변수명이나 함수명을 한번에 바꿀 때 사용하는 단축키 입니다. >> F2 2. Primary SideBar Toggle VisualStudio 에서 가르키는 SideBar는 아래 이미지의 왼쪽 부분을 말하는데요. 파일등의 이동이나 생성시에 사용하게 되구요. 확장프로그램설치할 때도 사용합니다. 코딩할 때는 닫았다가, 파일생성등을 할 때는 아래 단축키로 닫으면 편합니다. >> Command + B 2023. 4. 23. 공유되는 Route 의 Navigation 구현방법 # Jetpack Compose Jetpack Compose의 Navigation을 사용할 때, 페이지가 교차되는 Route 가 존재하는 경우가 생기게 되는데요. 오늘은 이런 교차되는 혹은 공유되는 Route 의 구현 방법에 대해서 정리해 보도록 하겠습니다. 다만, 이 글은 Jetpack Compose 의 Navigation의 기본 구현방법에 대해서는 다루지 않습니다. 1. 큰 그림 다음과 같은 형태의 NavigationGraph 가 있다고 가정해 보겠습니다. Main페이지에는 Home과 Profile 이 존재하는데, 둘다 같은 Settings로 routing 되어야 합니다. Home -> Settings Profile -> Settings 2023. 4. 22. getLaunchIntentForPackage 로 다른 앱을 실행 하는 방법 # query Android 11 오늘은 안드로이드 앱에서 다른 앱을 launch 시키는 방법을 정리해 보도록 하겠습니다. 1. Query Manifest 적용 Android11(API30)이 발표된 이래로, 앱내에서 다른 앱을 실행하는 경우 Manifest에서, 다른 앱에 대한 패키지 명을 명시해 주어야만 합니다. 예전에는 없었던 작업이므로, 간혹 오래된 앱들은 이것이 선언되지 않아서, 기능이 작동하지 않을 수 있습니다. 혹시 잘 되던 기능이 않되고 있다면 이 부분을 체크해 주어야 합니다. manifest 에 선언하는 것이므로, Enum 클래스를 사용할 수 없습니다. 하드코딩해야 하므로, 테스트를 반드시 해 주어야 합니다. ... ... 2023. 4. 20. NodeJS Import 방법 정리 # CommonJS ES 오늘은 Nodejs에서 사용되는 import 을 정리해 보도록 하겠습니다. 그중에서도 가장 오랫동안 사용되어온 CommonJS의 모듈화 방식과, 최신의 ES방식에 대해서 정리해 보도록 하겠습니다. 먼저 CommonJS의 방법을 정리해 보도록 하겠습니다. 1. CommonJS 1-1. export 예를 들어, 프로젝트의 폴더에서 /util 폴더에 아래와 같이 코딩하고, hello.js 파일을 만들었다고 가정해 보겠습니다. export 할 변수명이나 함수명을 module.exports = 다음에 넣어주면 됩니다. function hello(name) { return `Hello World, I'm, $name`; } module.exports = hello; 2023. 4. 19. Coroutine suspend 동작에 관한 좋은 예와 잘못된 예 # 비동기 오늘은 Coroutine 은 기본적으로 비동기를 기본으로 합니다. 그렇기에 비동기 블록이 suspend 되어서 결과값을 받아서 실행해야 할 경우는, suspend 함수를 위치시킬 때 생각을 해야 합니다. 그렇지 않으면 예상치 못한 동작을 보게되는데요. 오늘은 suspend 함수를 이용해서 Coroutine을 잘 사용한 예와 잘못 사용한 예를 점검해 보겠습니다. 1. suspend 가 제대로 동작하지 않는 잘못된 케이스 우선은 코드를 보도록 하겠습니다. 아래에서 launch 블록바깥에 결과를 얻어서, print 하는 것이 의도인데요. 실제로 실행해 보면, result에 값이 담기기 전에 지나쳐 버립니다. suspend fun fetchData(): String { delay(2000) return "dat.. 2023. 4. 18. flatMapLatest 이용해서 값이 들어오는 것을 기다리기 # Coroutine 오늘은 flapMapLatest 를 활용해서 특정한 값이 들어오는 것을 기다리다가, 값이 들어오면 특정 코드를 실행시키는 방법에 대해서 정리해 보겠습니다. 물론, Coroutine 의 supsend 함수를 이용하면 어려운 일은 아니지만, 만약, Global 한 변수에 들어오는 값에 대해서라면 조금 다른 접근이 필요하기 때문입니다. 1. FlatMapLatest 이 API는 Kotlin Coroutine 의 Flow API인데요. Flow에서 흘러나오는 데이터 스트림에서, 가장 최근의 값만 취하기 위해서 만들어 졌습니다. 새로운 값이 흘러나오면 기존 flow는 cancel 시켜버리도록 되어 있습니다. 2. 값이 들어오는 것 기다리기 구현 가장 최근 값이 들어오는 것을 기다릴 때도, flatmapLates.. 2023. 4. 18. 서버가 되는 맥컴퓨터의 IP Address 보기 # Mac NodeJS등을 이용해서 간단한 테스트서버를 맥컴퓨터에 띄어서 안드로이드나 다른 컴퓨터에서 접속을 시험할 때, 해당 컴퓨터의 접속할 IP주소를 찾을 때 사용하는 명령어를 알아야 합니다. 오늘은 이 때 사용할 명령어에 대해서 정리해 보겠습니다. 1. IP주소 찾기 서버를 Localhost로 띄어 놓고 간단히 테스트를 하는 경우가 많은데요. 이 서버에 접속할 Android 나 다른 컴퓨터에서는 'localhost:3000' 나 '127.0.0.1:3000' 과 같은 주소로는 접속할 수 없어서, 서버가 되는 컴퓨터의 IP주소를 찾아야 합니다. 아래 명령어를 안드로이드 스튜디오의 터미널에서 실행해 주면, 접속할 수 있는 인터넷 주소가 191.x.x.x 과 같이 나오므로, 테스트 서버와 연결해서 안드로이드를 테스.. 2023. 4. 18. Paging Library 구현 방법 총정리 # Android 오늘은 Android Paging Library의 구현방법에 대해서 정리해 보도록 하겠습니다. 1. Library Implement 가장 먼저 할 것은 라이브러리 선언인데요. PagingLibrary는 다음의 것들을 선언해 주면 됩니다. def paging_version = "3.1.1" implementation "androidx.paging:paging-runtime:$paging_version" implementation "androidx.paging:paging-compose:1.0.0-alpha18" PagingLibrary를 어떻게 사용하느냐에 따라 다르지만, 대부분 서버와 로컬 DB를 필요로 하므로, 이를 기준으로 Room 과 Retrofit 에 대해서 라이브러리 설치와 설정이 이미 되어 있.. 2023. 4. 17. GeneratedInjector could not be resolved 에러 대응 방법 # package name 아주 오래된 프로젝트를 다시 빌드해 보니, hilt 와 관련해서 아래와 같은 에러로그를 보게 되었습니다. Fragment 에서 발생한 에러였는데, Hilt 에 관련한 셋업은 문제가 없었고, @AndroidEntryPoint 를 붙이는 것과 같은 기본적인 사항도 문제가 없었습니다. public abstract static class FragmentC implements TestFragment_GeneratedInjector, ^ symbol: class TestFragment_GeneratedInjector location: class MyApplication_HiltComponents error: ComponentProcessingStep was unable to process 'com.exam.test.. 2023. 4. 15. 함수안에 함수 넣기 # Closure Local functions Kotlin 오늘은 Kotlin에서 함수안에 함수를 넣는 것에 대해서 정리해 보도록 하겠습니다. 1. 함수안에 함수 코틀린에서는 local function 즉, 함수안에 다른 함수를 넣는 것이 가능합니다. fun main() { fun hello(name: String) { println("Hello, $name!") } hello("김군") hello("홍군") } 2. Clousure 함수안에 함수를 사용할 때는 Closure라는 개념이 들어갑니다. 쉽게 말해서, 함수 바깥의 범위에 들어가 있는 변수의 값에 접근하는 것이 가능하다는 것 입니다. 아래의 calculateDiscount 함수는 바깥에 있는 price와 discountPercentage에 접근이 가능한 것도 이것 때문입니다. 원래는 global한 범위.. 2023. 4. 15. 데이터보안 양식 잘못됨 SPLIT_BUNDLE 13 정책 선언 # Google Play 가끔 사용자정보를 수집하지 않고 있는데, Google 에서 정책준수 위반 메일을 받으신 분들이 있습니다. 오늘은 이렇게 데이터보안 양식 관련 정책 위반을 받고 앱게시 중단에 대해서 정리해 보겠습니다. 1. SPLIT_BUNDLE 13: 정책 선언 어느 날 이런 메일을 받게 됩니다. 내용을 정리하면, 사용자 정보를 수집하는데, 안 하고 있다고 하였다. 그러니 수정하라는 것 입니다. 2023. 4. 14. 함수 실행 시간 측정 후 Delay 사용하기 오늘은 함수의 실행시간을 체크하여서, 특정시간을 지나지 않으면, 그 만큼 Delay를 시키는 코드를 정리해 보겠습니다. 이런 코드는 특히 Splash화면 진행중에 하기에 용이한데요. 예를 들어 Splash를 3초정도 보여주고 싶다면, 특정 코드를 실행시키고 3초에서 남은 시간을 측정한 후, 나머지 시간만큼 Delay를 부여해 준다음 화면을 보여주기만 하면 되겠지요. 1. 함수 실행시간 측정하기 함수의 실행시간 측정은 아래와 같이 할 수 있습니다. 먼저 deleteFiles()라는 함수를 실행하였고요. 바로 직전과 실행후의 시간을 측정합니다. 시간 측정은 system.currentTimeMillis 를 활용하였습니다. 아래와 같은 코드를 이용하면, 최대 시간인 maxTimeMillis로부터, 걸린 시간(e.. 2023. 4. 14. 안드로이드 앱 내부 파일 확장자 체크 후 삭제하기 # delete filesDir 안드로이드에서는 앱에서 사용할 디렉토리를 따로 정해주었는데요. 이러한 내부에 파일들에 사용하지 않는 이미지들을 저장했다가 지워야할 때가 있습니다. 앱 내부의 디비에서는 이미지 uri를 가지고 있다가 유저가 해당 데이터를 지웠는데, 이 때, 내부 디렉토리의 파일로는 남아있는 경우입니다. 1. FilesDir 와 CacheDir 안드로이드에서는 내부파일에 접근하기 위한 장소로 FilesDir를 제공해주고 있는데요. 이곳은 완벽하게 다른 앱들은 접근할 수 없는 앱만의 영역으로, 앱이 필요로 하는 데이터나 캐쉬파일들을 저장할 수 있습니다. API29버전 이상부터 이 영역은 암호화 되어서 저장되게 됩니다. 그리고 CacheDir은 이름에서도 알 수 있듯이, 앱에서 사용할 캐쉬파일들을 저장하는 곳 입니다. 당연히 .. 2023. 4. 14. Route 에서 전달된 값을 ViewModel 에서 받는 방법 # Jetpack Compose Navigaion savedStateHandle 오늘은 Route에서 넘어온 값을 ViewModel 에서 savedStateHandle 을 이용해 받아서 사용하는 방법에 대해서 정리해 보겠습니다. 1. Route에서 값을 넘겨줄 때 먼저 route에서 값을 넘겨주는 코드를 보고 가겠습니다. testId를 route에 실어서 보내주는 코드인데요. TestId는 ViewModel에서 받아서 사용할 것이므로, TestPage에 인자로 넘겨주지 않았습니다. ... composable( route = "test_route" + "/{testId}", arguments = listOf( navArgument("testId") { type = NavType.LongType defaultValue = -8889L }, ), ) { TestPage() } 2023. 4. 13. Kotlin custom getter 와 setter 구현하기 Kotlin 에서는 Custom 하게 getter 와 setter 를 구현할 수 있는데요. 오늘은 이것에 대해서 알아보고, 활용 사례까지 정리해 보겠습니다. 1. Custom Getter 와 Setter Kotlin은 우리가 특별히 무엇을 하지 않아도, 알아서 getter 와 setter 를 만들어 줍니다. Java에서는 class의 길이가 길었었는데, 덕분에 Kotlin에서는 매우 간결해 졌습니다. 그런데, 우리는 때로 override 해서 다른 방식으로 get하거나 set을 해야할 때가 있는데요. 이것을 하는 것을 보도록 하겠습니다. 1-1. Custom Getter 아래와 같이 age를 정의할 경우, custom하게 getter를 설정하려면 아래와 같이 해주면 됩니다. get()을 넣어주고, 수식으.. 2023. 4. 11. collectAsState 로 Flow 타입 데이터 받기 구현 방법 # Jetpack Compose 오늘은 collectAsState 로 Flow 타입 데이터를 받는 것을 구현하는 방법에 대해서 정리해 보도록 하겠습니다. 1. CollectAsState 구현 collectAsState 을 사용하게 되는 시나리오에 대해서 정리해 보겠습니다. RoomDB등을 이용한 Repository에서 Flow타입의 데이터가 흘러나온다고 가정해 보겠습니다. 이 때 viewModel에서 해당 데이터를 받습니다. 그리고 Jetpack Compose UI 에서 그 데이터를 collectAsState형태로 받게 됩니다. 하나씩 보도록 하겠습니다. 1-1. Repository 에서 흘러나오는 데이터 관찰 먼저 roomDB에서 아래와 같은 Repository가 있다고 가정해 보겠습니다. flow는 비동기적으로 데이터의 강을 obs.. 2023. 4. 10. isNullOrEmpty 와 isNullOrBlank 의 차이점 # Kotlin 오늘은 Kotlin의 isNullOrEmpty 와 isNullOrBlank 의 차이점에 대해서 정리해 보겠습니다. 둘은 비슷하기 때문에 잘 알지 않고 사용하는 경우도 있는데요. 오늘 차이를 구분해 보도록 하겠습니다. 1. isNullOrEmpty 이 API는 만약 String이 null 이거나 empty 일 경우 true를 반환해 준다고 설명되어 있습니다. 그런데, empty는 어떤 것을 말하는 것일까요? 비어있다는 뜻 입니다. 즉 ""을 가리킵니다. String 값에 대해서 ""로 초기화하는 경우도 있는데요. 그대로 아무 값이 없는지 보려면 isNullOrEmpty로 충분합니다. val xInitValue: String? = "" println(xInitValue.isNullOrEmpty()) // t.. 2023. 4. 9. MutableStateFlow 이용한 로딩 후 로딩 완료 기다리기 구현 방법 AdView처럼 라이브러리에 로딩을 시키고, 해당 로딩이 다 될 때까지 기다릴 때 어떻게 구현해야 하는지 정리해 보겠습니다. 1. Mode 설정 먼저 여러가지 모드를 설정하기 위해서 다음과 같이 Mode에 관한 sealed class를 하나 작성해 줍니다. 이것은 로딩에서 로드가 완료되었을 때를 알기 위해서 인데요. 광고라이브러리에서는 보통 콜백으로 로드가 다 되었을 때 호출되는 onAdLoaded 와 같은 함수를 제공해 주므로, 이러한 콜백함수에서 Mode를 Loaded로 해주고, 로딩을 시작할 때, Loading으로 모드를 설정해 주면 되겠지요. sealed class AdMode { object Loading : AdMode() object Loaded : AdMode() data class Err.. 2023. 4. 8. lateinit 에 관한 정리 # Kotlin 오늘은 Kotlin 의 lateinit 에 대해서 정리해 보도록 하겠습니다. 1. lateinit lateinit은 키워드 자체로 설명이 되어있는데요. 초기화(initialize)가 late하게 된다는 의미를 가지고 있습니다. 이 키워드를 사용하면, 컴파일러는 변수 선언시에 초기화가 되지 않아도 아무런 에러를 보여주지 않구요. 개발자가 원하는 시점에 초기화를 할 수 있도록 해 줍니다. 게다가 lateinit 을 이용해서 선언할 경우, onDestroy에서 null 로 다시 값을 넣어줄 필요가 없습니다. 이 객체는 자동으로 가비지콜렉터에 의해서 해당변수가 포함된 클래스가 destroy 될 때 함께 처리됩니다. 예를 들어서 아래와 같이 adView나 adRequest를 초기화 하지 않은 상태에서 미리 선언하.. 2023. 4. 7. 이전 1 2 3 4 5 6 ··· 13 다음