본문 바로가기
개발

📱 Kotlin과 Android Studio로 시작하는 모바일 앱 개발: 초보자를 위한 스텝 바이 스텝 가이드

by D-Project 2025. 5. 29.

안녕하세요 🌟 오늘은 Kotlin 언어와 Android Studio를 활용하여 처음부터 안드로이드 앱을 개발하는 방법을 단계별로 배워보겠습니다. 코딩 경험이 적어도 걱정하지 마세요. 이 가이드는 여러분이 첫 앱을 성공적으로 만들 수 있도록 친절하게 안내해 드릴 거예요!

📋 목차

Kotlin과 Android 개발의 기초

모바일 앱 개발 여정을 시작하기 전에, Kotlin과 Android 개발이 무엇인지 간략히 살펴보겠습니다.

Kotlin이란? 🤔

Kotlin은 JetBrains에서 개발한 현대적인 프로그래밍 언어로, 2017년부터 Google이 Android 개발을 위한 공식 언어로 지정했습니다. 다음과 같은 주요 특징이 있습니다:

  • 간결한 문법: Java보다 적은 코드로 더 많은 일을 할 수 있습니다
  • Null 안전성: NullPointerException을 컴파일 시점에 방지합니다
  • 함수형 프로그래밍 지원: 람다식, 고차 함수 등 지원
  • Java와의 완벽한 호환성: 기존 Java 코드와 함께 사용 가능
  • 코루틴 지원: 비동기 프로그래밍을 간편하게 구현

Kotlin vs Java 비교 ⚖️

기능 Kotlin Java
널 안전성 기본 내장 (? 연산자) Optional 또는 수동 체크 필요
확장 함수 지원 미지원
속성 간결한 getter/setter Boilerplate 코드 필요
데이터 클래스 한 줄로 정의 가능 많은 코드 필요
스마트 캐스트 지원 미지원
코루틴 기본 지원 CompletableFuture/Threads
불변성 val/var 구분 final 키워드 필요

Android 앱 개발 특징 📊

Android 앱 개발의 주요 특징과 장점:

  • 거대한 사용자 기반: 전 세계 약 25억 대의 Android 기기
  • 풍부한 API와 라이브러리: 다양한 기능을 쉽게 구현 가능
  • 구글 플레이 스토어: 앱 배포 및 수익창출 플랫폼
  • 다양한 기기 지원: 스마트폰, 태블릿, TV, 웨어러블 등
  • 개방성: 제약이 적고 커스터마이징 자유도가 높음

안드로이드 아키텍처 컴포넌트 🏗️

안드로이드 앱 개발의 핵심 구성 요소:

  • 액티비티(Activity): 사용자와 상호작용하는 화면 단위
  • 프래그먼트(Fragment): 재사용 가능한 UI 컴포넌트
  • 서비스(Service): 백그라운드에서 실행되는 컴포넌트
  • 브로드캐스트 리시버(Broadcast Receiver): 시스템 이벤트 수신
  • 콘텐트 프로바이더(Content Provider): 데이터 공유 메커니즘
  • 인텐트(Intent): 컴포넌트 간 통신 메커니즘

개발 환경 설정하기

Android 앱 개발을 시작하기 위해 필요한 도구를 설치하고 설정해 봅시다.

Android Studio 설치 🛠️

  1. Android Studio 다운로드:
  2. 설치 과정:
    • 다운로드한 설치 파일 실행
    • 설치 마법사 따라 진행 (기본 설정 권장)
    • Android SDK, 에뮬레이터, Kotlin 플러그인 등 함께 설치
  3. 시스템 요구사항:
항목 최소 사양 권장 사양
운영체제 Windows 7/8/10, macOS 10.14+, Linux Windows 10/11, macOS 12+, Linux
RAM 8GB 16GB 이상
디스크 공간 4GB 10GB 이상 (SDK/에뮬레이터 포함)
화면 해상도 1280 x 800 1920 x 1080 이상
Java/Kotlin Java 8+, Kotlin 포함됨 최신 버전

가상 기기(에뮬레이터) 설정 📱

실제 기기 없이도 앱을 테스트할 수 있는 가상 기기를 설정합니다:

  1. AVD(Android Virtual Device) 생성:
    • Android Studio 실행
    • Tools > Device Manager 클릭
    • Create Virtual Device 버튼 클릭
  2. 기기 선택:
    • 테스트할 기기 유형 선택 (Phone, Tablet, Wear OS 등)
    • 기기 모델 선택 (예: Pixel 6)
  3. 시스템 이미지 선택:
    • 테스트할 Android 버전 선택
    • Google Play 서비스가 필요하면 Google APIs 포함된 이미지 선택
    • Download 클릭하여 다운로드 (처음 한 번만)
  4. AVD 구성:
    • 이름 지정 및 추가 설정 (선택사항)
    • 하드웨어 속성 조정 (RAM, 저장소 등)
    • Finish 클릭하여 완료

실제 기기 설정 (선택사항) 📲

실제 Android 기기로 테스트하려면:

  1. 개발자 옵션 활성화:
    • 기기의 설정 > 휴대전화 정보 > 소프트웨어 정보 > 빌드 번호를 7번 탭
    • "개발자가 되었습니다" 메시지 확인
  2. USB 디버깅 활성화:
    • 설정 > 개발자 옵션 > USB 디버깅 활성화
    • USB 케이블로 기기 연결 (데이터 전송 모드)
    • 컴퓨터에서 기기 인식 확인 (드라이버 설치 필요할 수 있음)
  3. ADB(Android Debug Bridge) 확인:
    • Android Studio에서 Tools > Device Manager 클릭
    • 연결된 실제 기기 확인

첫 번째 Android 프로젝트 만들기

이제 첫 번째 Android 프로젝트를 생성해 봅시다!

새 프로젝트 생성 🚀

  1. Android Studio 시작화면:
    • New Project 클릭
    • 또는 이미 실행 중이라면 File > New > New Project
  2. 프로젝트 템플릿 선택:
    • Empty Activity 선택 (가장 기본적인 템플릿)
    • Next 클릭
  3. 프로젝트 구성:
    • Name: 앱 이름 입력 (예: "MyFirstApp")
    • Package name: 앱 식별자 (예: "com.example.myfirstapp")
    • Save location: 프로젝트 저장 위치
    • Language: Kotlin 선택
    • Minimum SDK: API 23: Android 6.0 (Marshmallow) 권장
      • 낮은 API = 더 많은 기기 지원, 높은 API = 더 많은 기능
    • Finish 클릭

프로젝트 구조 탐색 📂

Android Studio에서 생성된 프로젝트의 주요 구성 요소:

MyFirstApp/
├── app/
│   ├── manifests/
│   │   └── AndroidManifest.xml  (앱 구성 정보)
│   ├── java/
│   │   └── com.example.myfirstapp/
│   │       ├── MainActivity.kt  (메인 액티비티 코드)
│   │       └── ...
│   └── res/
│       ├── drawable/  (이미지 리소스)
│       ├── layout/
│       │   └── activity_main.xml  (UI 레이아웃)
│       ├── values/
│       │   ├── colors.xml  (색상 정의)
│       │   ├── strings.xml  (문자열 정의)
│       │   └── themes.xml  (테마 정의)
│       └── ...
├── gradle/
├── build.gradle  (프로젝트 빌드 설정)
└── ...

첫 번째 앱 실행하기 ▶️

  1. 빌드 및 실행:
    • 상단 툴바의 녹색 ▶️(Run) 버튼 클릭
    • 또는 Shift + F10 (Windows/Linux) / Control + R (macOS)
  2. 실행 대상 선택:
    • 에뮬레이터 또는 연결된 실제 기기 선택
    • OK 클릭
  3. 앱 실행 확인:
    • 에뮬레이터/기기에서 앱 로딩 확인
    • "Hello World!" 텍스트가 표시된 화면 확인

🎉 축하합니다! 첫 번째 Android 앱을 성공적으로 만들고 실행했습니다!

Android 앱 구조 이해하기

Android 앱의 핵심 구성 요소와 작동 방식을 이해해 봅시다.

AndroidManifest.xml 파일 📜

AndroidManifest.xml은 앱의 필수 정보를 시스템에 제공하는 중요한 파일입니다:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myfirstapp">

    <!-- 앱 권한 선언 -->
    <!-- <uses-permission android:name="android.permission.INTERNET" /> -->

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyFirstApp">

        <!-- 액티비티 선언 -->
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

주요 요소:

  • 패키지 이름: 앱의 고유 식별자
  • 권한(Permissions): 앱이 필요로 하는 시스템 권한
  • 애플리케이션 요소: 아이콘, 라벨, 테마 등 설정
  • 컴포넌트 선언: 액티비티, 서비스 등 앱 구성 요소 선언
  • 인텐트 필터: 컴포넌트가 응답할 인텐트 유형 정의

Activity와 생명주기 🔄

액티비티(Activity)는 사용자 인터페이스 화면을 표현하며, 특정 생명주기를 갖습니다:

class MainActivity : AppCompatActivity() {

    // 액티비티 생성 시 호출
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 초기화 코드
        Log.d("MainActivity", "onCreate called")
    }

    // 액티비티가 화면에 보이기 시작할 때 호출
    override fun onStart() {
        super.onStart()
        Log.d("MainActivity", "onStart called")
    }

    // 액티비티가 사용자와 상호작용할 수 있을 때 호출
    override fun onResume() {
        super.onResume()
        Log.d("MainActivity", "onResume called")
    }

    // 다른 액티비티가 포그라운드로 올 때 호출
    override fun onPause() {
        super.onPause()
        Log.d("MainActivity", "onPause called")
    }

    // 액티비티가 더 이상 보이지 않을 때 호출
    override fun onStop() {
        super.onStop()
        Log.d("MainActivity", "onStop called")
    }

    // 액티비티가 소멸될 때 호출
    override fun onDestroy() {
        super.onDestroy()
        Log.d("MainActivity", "onDestroy called")
    }
}

Activity 생명주기

리소스 관리 🎨

Android 앱은 코드와 리소스를 분리하여 관리합니다:

  1. 레이아웃(Layout): res/layout/ 디렉토리에 XML 파일로 정의
  2. <!-- activity_main.xml --> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
  3. 문자열(Strings): res/values/strings.xml에 정의
  4. <resources> <string name="app_name">My First App</string> <string name="welcome_message">Welcome to Android!</string> </resources>
  5. 색상(Colors): res/values/colors.xml에 정의
  6. <resources> <color name="purple_200">#FFBB86FC</color> <color name="purple_500">#FF6200EE</color> <color name="purple_700">#FF3700B3</color> <color name="teal_200">#FF03DAC5</color> <color name="teal_700">#FF018786</color> <color name="black">#FF000000</color> <color name="white">#FFFFFFFF</color> </resources>
  7. 이미지(Drawables): res/drawable/ 디렉토리에 저장
    • 벡터 드로어블(.xml)
    • 비트맵 이미지(.png, .jpg)
    • 9-patch 이미지(.9.png)
  8. 리소스 접근 방법:
    • XML에서: @string/welcome_message, @color/purple_500
    • Kotlin 코드에서: R.string.welcome_message, R.color.purple_500

UI 디자인 기초

Android 앱의 UI(사용자 인터페이스)를 디자인하는 방법을 알아봅시다.

레이아웃 유형 🧩

Android에서 사용할 수 있는 주요 레이아웃 유형:

  1. ConstraintLayout: 유연한 UI 배치를 위한 현대적인 레이아웃
  2. <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <!-- 자식 뷰들 --> </androidx.constraintlayout.widget.ConstraintLayout>
  3. LinearLayout: 가로/세로 방향으로 요소를 차례로 배치
  4. <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- 자식 뷰들 --> </LinearLayout>
  5. FrameLayout: 단일 항목 표시를 위한 간단한 레이아웃
  6. <FrameLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- 자식 뷰들 --> </FrameLayout>
  7. RelativeLayout: 상대적 위치로 요소 배치
  8. <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <!-- 자식 뷰들 --> </RelativeLayout>

기본 UI 컴포넌트 🧰

자주 사용되는 UI 컴포넌트들:

  1. TextView: 텍스트 표시
  2. <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="18sp" android:textColor="@color/black" />
  3. Button: 클릭 가능한 버튼
  4. <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Click Me" />
  5. EditText: 텍스트 입력 필드
  6. <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Enter your name" android:inputType="text" />
  7. ImageView: 이미지 표시
  8. <ImageView android:id="@+id/imageView" android:layout_width="100dp" android:layout_height="100dp" android:src="@drawable/my_image" android:contentDescription="Sample image" />
  9. RecyclerView: 스크롤 가능한 목록
  10. <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" />

레이아웃 편집기 사용하기 🎨

Android Studio의 레이아웃 편집기를 효과적으로 사용하는 방법:

  1. 디자인/코드 모드 전환:
    • 상단에 있는 'Design', 'Code', 'Split' 탭으로 전환
    • 'Split' 모드: 코드와 디자인 미리보기 동시에 보기
  2. 컴포넌트 추가:
    • 왼쪽 팔레트에서 원하는 컴포넌트 드래그 앤 드롭
    • XML에 직접 코드 추가
  3. 속성 편집:
    • 오른쪽 속성 패널에서 선택한 컴포넌트 속성 변경
    • ID, 레이아웃 크기, 마진, 패딩, 텍스트 등 설정
  4. 제약 조건 설정 (ConstraintLayout 사용 시):
    • 컴포넌트 선택 후 앵커 포인트를 드래그하여 부모 또는 다른 컴포넌트에 연결
    • 마진 값 설정으로 간격 조정
  5. 미리보기 옵션:
    • 다양한 기기, 테마, 언어로 미리보기
    • 가로/세로 모드 전환
    • 야간 모드 미리보기

Kotlin 언어 기초

Android 앱 개발에 필요한 Kotlin 언어의 기본 요소를 알아봅시다.

변수와 타입 📊

// 변수 선언 (타입 추론)
val name = "John"      // 변경 불가능한 변수 (final)
var age = 25           // 변경 가능한 변수

// 명시적 타입 지정
val height: Float = 175.5f
var isStudent: Boolean = true

// 기본 타입
val byte: Byte = 127           // 8-bit 정수
val short: Short = 32767       // 16-bit 정수
val int: Int = 2147483647      // 32-bit 정수
val long: Long = 9223372036854775807L  // 64-bit 정수

val float: Float = 3.14f       // 32-bit 부동소수점
val double: Double = 3.14159   // 64-bit 부동소수점

val char: Char = 'A'           // 문자
val string: String = "Hello"   // 문자열

val boolean: Boolean = true    // 불리언

// Null 가능 타입
val nullableName: String? = null  // null 가능

함수 정의 🔧

// 기본 함수
fun greet(name: String): String {
    return "Hello, $name!"
}

// 한 줄 함수 (반환 타입 추론)
fun add(a: Int, b: Int) = a + b

// 기본 매개변수 값
fun greetWithDefault(name: String = "Guest"): String {
    return "Hello, $name!"
}

// 가변 인자
fun sum(vararg numbers: Int): Int {
    var total = 0
    for (number in numbers) {
        total += number
    }
    return total
}

// 함수 호출 예시
val message = greet("Alice")
val result = add(5, 3)
val defaultGreeting = greetWithDefault()
val total = sum(1, 2, 3, 4, 5)

제어 흐름 🔀

// if-else 문
val score = 85
val grade = if (score >= 90) {
    "A"
} else if (score >= 80) {
    "B"
} else if (score >= 70) {
    "C"
} else {
    "D"
}

// when 표현식 (강화된 switch)
val dayOfWeek = 3
val day = when (dayOfWeek) {
    1 -> "Monday"
    2 -> "Tuesday"
    3 -> "Wednesday"
    4 -> "Thursday"
    5 -> "Friday"
    6, 7 -> "Weekend"
    else -> "Invalid day"
}

// for 루프
val fruits = listOf("Apple", "Banana", "Cherry")
for (fruit in fruits) {
    println(fruit)
}

// 범위를 사용한 for 루프
for (i in 1..5) {
    println(i)  // 1, 2, 3, 4, 5
}

for (i in 5 downTo 1) {
    println(i)  // 5, 4, 3, 2, 1
}

for (i in 1..10 step 2) {
    println(i)  // 1, 3, 5, 7, 9
}

// while 루프
var counter = 0
while (counter < 5) {
    println(counter)
    counter++
}

// do-while 루프
var num = 10
do {
    println(num)
    num--
} while (num > 5)

클래스와 객체 🧬

// 기본 클래스
class Person {
    var name: String = ""
    var age: Int = 0

    fun introduce() {
        println("Hi, I'm $name and I'm $age years old.")
    }
}

// 생성자가 있는 클래스
class Student(var name: String, var age: Int, var grade: Int) {

    // 보조 생성자
    constructor(name: String, age: Int): this(name, age, 1)

    fun study() {
        println("$name is studying.")
    }
}

// 데이터 클래스 (equals, hashCode, toString 자동 구현)
data class User(val id: Int, val name: String, val email: String)

// 싱글톤 객체
object Logger {
    fun log(message: String) {
        println("LOG: $message")
    }
}

// 클래스 사용 예시
val person = Person()
person.name = "John"
person.age = 25
person.introduce()

val student = Student("Alice", 20, 3)
student.study()

val user = User(1, "Bob", "bob@example.com")
println(user.toString())  // User(id=1, name=Bob, email=bob@example.com)

Logger.log("Something happened")

컬렉션 📚

// 리스트 (불변)
val numbers = listOf(1, 2, 3, 4, 5)
println(numbers[0])  // 1

// 가변 리스트
val mutableNumbers = mutableListOf(1, 2, 3)
mutableNumbers.add(4)
mutableNumbers.removeAt(0)

// 세트 (중복 없음)
val uniqueNumbers = setOf(1, 2, 3, 3, 4)  // [1, 2, 3, 4]
val mutableSet = mutableSetOf(1, 2, 3)

// 맵 (키-값 쌍)
val userAges = mapOf("John" to 25, "Alice" to 20)
println(userAges["John"])  // 25

val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3

고차 함수와 람다 🌟

// 람다 표현식
val sum = { a: Int, b: Int -> a + b }
println(sum(5, 3))  // 8

// 고차 함수 (함수를 파라미터로 받음)
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
    return operation(a, b)
}

val resultAdd = calculate(5, 3) { a, b -> a + b }
val resultMultiply = calculate(5, 3) { a, b -> a * b }

// 컬렉션 함수형 API
val numbers = listOf(1, 2, 3, 4, 5)

// 필터링
val evenNumbers = numbers.filter { it % 2 == 0 }  // [2, 4]

// 매핑
val doubled = numbers.map { it * 2 }  // [2, 4, 6, 8, 10]

// 집계
val sum = numbers.sum()  // 15
val product = numbers.reduce { acc, i -> acc * i }  // 120

// 체이닝
val sumOfEvenSquares = numbers
    .filter { it % 2 == 0 }
    .map { it * it }
    .sum()  // 2² + 4² = 20

Null 안전성 🛡️

// Nullable 타입
var name: String? = "John"
name = null  // 가능

// Safe call 연산자
val length = name?.length  // name이 null이면 length도 null

// Elvis 연산자
val nonNullLength = name?.length ?: 0  // name이 null이면 0 반환

// Non-null 단언 연산자 (주의: NullPointerException 발생 가능)
val definitelyLength = name!!.length

// 스마트 캐스트
fun getLength(str: String?): Int {
    if (str != null) {
        // str은 여기서 자동으로 String 타입으로 캐스트됨
        return str.length
    }
    return 0
}

// let 함수로 null 아닌 경우만 처리
name?.let {
    println("Name is $it with length ${it.length}")
}

간단한 앱 기능 구현하기

기본적인 앱 기능을 구현하는 방법을 알아봅시다.

버튼 클릭 이벤트 처리 👆

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ID로 버튼 참조
        val button = findViewById<Button>(R.id.button)

        // 클릭 리스너 설정
        button.setOnClickListener {
            // 클릭 시 실행할 코드
            Toast.makeText(this, "Button clicked!", Toast.LENGTH_SHORT).show()
        }

        // 길게 누르기 리스너 설정
        button.setOnLongClickListener {
            Toast.makeText(this, "Button long-pressed!", Toast.LENGTH_SHORT).show()
            true  // 이벤트 소비됨을 나타냄
        }
    }
}

ViewBinding 사용하기 🔗

ViewBinding을 사용하면 findViewById() 없이 더 안전하게 뷰에 접근할 수 있습니다:

  1. build.gradle에 설정 추가:
  2. android { // ... buildFeatures { viewBinding true } }
  3. ViewBinding 사용:
  4. class MainActivity : AppCompatActivity() { // 바인딩 객체 선언 private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 바인딩 초기화 binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // ID 없이 직접 뷰 참조 binding.button.setOnClickListener { binding.textView.text = "Button clicked!" Toast.makeText(this, "Button clicked!", Toast.LENGTH_SHORT).show() } } }

Intent로 화면 전환하기 🔄

새로운 액티비티로 이동하는 방법:

  1. 새 액티비티 추가:
    • File > New > Activity > Empty Activity
    • 이름 입력 (예: "SecondActivity")
  2. Intent로 화면 전환:
  3. // MainActivity.kt binding.buttonGoToSecond.setOnClickListener { // 인텐트 생성 val intent = Intent(this, SecondActivity::class.java) // 데이터 전달 (선택사항) intent.putExtra("message", "Hello from MainActivity!") // 액티비티 시작 startActivity(intent) }

// SecondActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)

// 전달된 데이터 받기
val message = intent.getStringExtra("message") ?: "No message"
findViewById<TextView>(R.id.textViewMessage).text = message

}


### RecyclerView로 목록 표시하기 📋

RecyclerView를 사용하여 스크롤 가능한 목록을 구현하는 방법:

1. **build.gradle에 의존성 추가**:

dependencies {
implementation 'androidx.recyclerview:recyclerview:1.2.1'
}


2. **레이아웃에 RecyclerView 추가**:

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />


3. **항목 레이아웃 생성** (item_user.xml):

 

<TextView
    android:id="@+id/textViewName"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="18sp"
    android:textStyle="bold" />

<TextView
    android:id="@+id/textViewEmail"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="14sp" />

```

  1. 데이터 모델 정의:
  2. data class User(val name: String, val email: String)
  3. 어댑터 생성:
  4. class UserAdapter(private val users: List<User>) : RecyclerView.Adapter<UserAdapter.ViewHolder>() { class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val nameTextView: TextView = view.findViewById(R.id.textViewName) val emailTextView: TextView = view.findViewById(R.id.textViewEmail) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_user, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val user = users[position] holder.nameTextView.text = user.name holder.emailTextView.text = user.email // 항목 클릭 이벤트 처리 holder.itemView.setOnClickListener { Toast.makeText( holder.itemView.context, "Clicked on ${user.name}", Toast.LENGTH_SHORT ).show() } } override fun getItemCount() = users.size }
  5. RecyclerView 설정:
  6. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) // 샘플 데이터 생성 val users = listOf( User("John Doe", "john@example.com"), User("Jane Smith", "jane@example.com"), User("Bob Johnson", "bob@example.com"), // 더 많은 사용자 추가... ) // 레이아웃 매니저 설정 recyclerView.layoutManager = LinearLayoutManager(this) // 어댑터 설정 recyclerView.adapter = UserAdapter(users) }

데이터 저장 및 관리

Android 앱에서 데이터를 저장하고 관리하는 방법을 알아봅시다.

SharedPreferences 💾

간단한 키-값 데이터를 저장하는 방법:

// 데이터 저장
val sharedPref = getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
with(sharedPref.edit()) {
    putString("username", "John")
    putInt("age", 25)
    putBoolean("isLoggedIn", true)
    apply()  // 비동기 저장 (또는 commit()으로 동기 저장)
}

// 데이터 읽기
val username = sharedPref.getString("username", "")  // 기본값: 빈 문자열
val age = sharedPref.getInt("age", 0)  // 기본값: 0
val isLoggedIn = sharedPref.getBoolean("isLoggedIn", false)  // 기본값: false

Room 데이터베이스 🏬

SQLite 기반의 데이터베이스 사용 방법:

  1. build.gradle에 의존성 추가:
  2. dependencies { def room_version = "2.4.3" implementation "androidx.room:room-runtime:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" }
  3. 엔티티 정의:
  4. @Entity(tableName = "users") data class User( @PrimaryKey(autoGenerate = true) val id: Int = 0, val name: String, val email: String, val age: Int )
  5. DAO(Data Access Object) 인터페이스 생성:
  6. @Dao interface UserDao { @Query("SELECT * FROM users") fun getAll(): List<User> @Query("SELECT * FROM users WHERE id = :userId") fun getUserById(userId: Int): User @Insert fun insert(user: User): Long @Update fun update(user: User) @Delete fun delete(user: User) }
  7. 데이터베이스 클래스 정의:
  8. @Database(entities = [User::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao companion object { @Volatile private var INSTANCE: AppDatabase? = null fun getDatabase(context: Context): AppDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, "app_database" ).build() INSTANCE = instance instance } } } }
  9. 데이터베이스 사용:
  10. class MainActivity : AppCompatActivity() { private lateinit var db: AppDatabase private lateinit var userDao: UserDao override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // 데이터베이스 초기화 db = AppDatabase.getDatabase(applicationContext) userDao = db.userDao() // ⚠️ 주의: 메인 스레드에서 DB 작업 수행 시 오류 발생 // 코루틴 또는 다른 스레드에서 실행해야 함 // 예: 코루틴 활용 lifecycleScope.launch(Dispatchers.IO) { // 사용자 추가 val userId = userDao.insert(User(name = "John", email = "john@example.com", age = 25)) // 모든 사용자 가져오기 val users = userDao.getAll() // UI 업데이트는 메인 스레드에서 withContext(Dispatchers.Main) { updateUI(users) } } } private fun updateUI(users: List<User>) { // RecyclerView 어댑터 업데이트 등 } }

Data Store 🔐

SharedPreferences의 현대적 대안으로, 코루틴과 Flow를 지원합니다:

  1. build.gradle에 의존성 추가:
  2. dependencies { implementation "androidx.datastore:datastore-preferences:1.0.0" }
  3. DataStore 사용:
  4. // DataStore 인스턴스 생성 val dataStore = createDataStore(name = "settings")

// 환경설정 키 정의
val USER_NAME_KEY = preferencesKey("user_name")
val IS_LOGGED_IN_KEY = preferencesKey("is_logged_in")

// 데이터 저장
lifecycleScope.launch {
dataStore.edit { preferences ->
preferences[USER_NAME_KEY] = "John"
preferences[IS_LOGGED_IN_KEY] = true
}
}

// 데이터 읽기 (Flow 활용)
lifecycleScope.launch {
dataStore.data.collect { preferences ->
val username = preferences[USER_NAME_KEY] ?: ""
val isLoggedIn = preferences[IS_LOGGED_IN_KEY] ?: false

    // UI 업데이트
    updateUI(username, isLoggedIn)
}

}



## API 연동하기

앱에서 인터넷을 통해 원격 API와 통신하는 방법을 알아봅시다.

### Retrofit으로 HTTP 통신 🌐

1. **build.gradle에 의존성 추가**:

dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
}


2. **인터넷 권한 추가** (AndroidManifest.xml):

```

  1. 데이터 모델 정의:
  2. data class Post( val id: Int, val userId: Int, val title: String, val body: String )
  3. API 인터페이스 정의:
  4. interface ApiService { @GET("posts") suspend fun getPosts(): List<Post> @GET("posts/{id}") suspend fun getPost(@Path("id") postId: Int): Post @POST("posts") suspend fun createPost(@Body post: Post): Post @PUT("posts/{id}") suspend fun updatePost(@Path("id") postId: Int, @Body post: Post): Post @DELETE("posts/{id}") suspend fun deletePost(@Path("id") postId: Int): ResponseBody }
  5. Retrofit 인스턴스 생성:
  6. object RetrofitClient { private const val BASE_URL = "https://jsonplaceholder.typicode.com/" // 로깅 인터셉터 (개발용) private val loggingInterceptor = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } private val client = OkHttpClient.Builder() .addInterceptor(loggingInterceptor) .build() private val retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build() val apiService: ApiService = retrofit.create(ApiService::class.java) }
  7. API 호출 및 결과 처리:
  8. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val recyclerView = findViewById<RecyclerView>(R.id.recyclerView) recyclerView.layoutManager = LinearLayoutManager(this) // 코루틴으로 API 호출 lifecycleScope.launch { try { // IO 스레드에서 네트워크 요청 val posts = withContext(Dispatchers.IO) { RetrofitClient.apiService.getPosts() } // 결과 처리 (예: RecyclerView 어댑터에 설정) recyclerView.adapter = PostAdapter(posts) } catch (e: Exception) { // 오류 처리 Toast.makeText( this@MainActivity, "Error: ${e.message}", Toast.LENGTH_LONG ).show() Log.e("MainActivity", "API 호출 오류", e) } } } }

이미지 로딩 라이브러리 (Glide/Coil) 🖼️

원격 이미지를 효율적으로 로드하기 위한 라이브러리:

  1. Glide 설정:
  2. dependencies { implementation 'com.github.bumptech.glide:glide:4.14.2' annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2' }
  3. Glide 사용:
  4. // 기본 사용법 Glide.with(context) .load("https://example.com/image.jpg") .into(imageView)

// 추가 옵션
Glide.with(context)
.load("https://example.com/image.jpg")
.placeholder(R.drawable.loading) // 로딩 중 표시할 이미지
.error(R.drawable.error) // 오류 시 표시할 이미지
.centerCrop() // 크기 조정 방법
.transition(DrawableTransitionOptions.withCrossFade()) // 페이드 효과
.into(imageView)


3. **Coil 설정** (Kotlin 최적화 라이브러리):

dependencies {
implementation "io.coil-kt:coil:2.2.2"
}


4. **Coil 사용**:

// 기본 사용법
imageView.load("https://example.com/image.jpg")

// 추가 옵션
imageView.load("https://example.com/image.jpg") {
crossfade(true)
placeholder(R.drawable.loading)
error(R.drawable.error)
transformations(CircleCropTransformation())
}


## 디버깅 및 테스트

Android 앱의 디버깅과 테스트 방법을 알아봅시다.

### Logcat으로 디버깅 🔍

Android Studio의 Logcat을 활용한 디버깅:

// 다양한 로그 레벨
Log.v(TAG, "Verbose log") // 상세 정보
Log.d(TAG, "Debug log") // 디버그 정보
Log.i(TAG, "Info log") // 일반 정보
Log.w(TAG, "Warning log") // 경고
Log.e(TAG, "Error log") // 오류

// 변수 값 출력
val username = "John"
Log.d(TAG, "Username: $username")

// 예외 정보 출력
try {
// 코드...
} catch (e: Exception) {
Log.e(TAG, "Operation failed", e)
}


Logcat 필터링 팁:
- 태그별 필터링: `TAG:MainActivity`
- 패키지별 필터링: `package:com.example.myapp`
- 우선순위별 필터링: Error, Warning 등
- 텍스트 검색: 특정 단어/문구 검색

### 중단점(Breakpoint) 활용 🛑

Android Studio에서 중단점을 설정하여 실시간 디버깅:

1. **중단점 설정**:
   - 코드 줄 번호 옆 여백 클릭
   - 또는 라인 위치 후 `Ctrl+F8` (Windows/Linux) / `⌘F8` (macOS)

2. **디버그 모드로 앱 실행**:
   - 상단 툴바의 Debug 버튼 클릭
   - 또는 `Shift+F9` (Windows/Linux) / `Control+D` (macOS)

3. **디버그 기능**:
   - Step Over (`F8`): 현재 라인 실행 후 다음 라인으로
   - Step Into (`F7`): 메소드 내부로 진입
   - Step Out (`Shift+F8`): 현재 메소드에서 빠져나옴
   - Resume (`F9`): 다음 중단점까지 계속 실행
   - 변수 값 검사: 디버그 창에서 변수 확인
   - 조건부 중단점: 특정 조건 충족 시에만 중단

### 단위 테스트 작성 🧪

JUnit을 사용한 단위 테스트:

1. **테스트 의존성 추가**:

dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.0.0'
}


2. **단위 테스트 작성**:

// src/test/java/com/example/myapp/CalculatorTest.kt
class CalculatorTest {

private lateinit var calculator: Calculator

@Before
fun setup() {
    calculator = Calculator()
}

@Test
fun addition_isCorrect() {
    val result = calculator.add(3, 4)
    assertEquals(7, result)
}

@Test
fun subtraction_isCorrect() {
    val result = calculator.subtract(10, 4)
    assertEquals(6, result)
}

@Test(expected = IllegalArgumentException::class)
fun divideByZero_throwsException() {
    calculator.divide(10, 0)
}

}


3. **테스트 실행**:
   - 테스트 클래스에서 우클릭 > `Run 'CalculatorTest'`
   - 또는 테스트 옆 실행 버튼 클릭

### UI 테스트 (Espresso) 🧮

Android UI 자동화 테스트:

1. **테스트 의존성 추가**:

dependencies {
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}


2. **UI 테스트 작성**:
```kotlin
// src/androidTest/java/com/example/myapp/MainActivityTest.kt
@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun testLoginButtonClick() {
        // 입력 필드에 텍스트 입력
        onView(withId(R.id.editTextUsername))
            .perform(typeText("user123"), closeSoftKeyboard())

        onView(withId(R.id.editTextPassword))
            .perform(typeText("pass123"), closeSoftKeyboard())

        // 버튼 클릭
        onView(withId(R.id.buttonLogin))
            .perform(click())

        // 결과 확인
        onView(withId(R.id.textViewResult))
            .check(matches(withText("Login successful")))
    }
}
  1. UI 테스트 실행:
    • 테스트 클래스에서 우클릭 > Run 'MainActivityTest'
    • 에뮬레이터 또는 실제 기기에서 테스트 실행

앱 배포 준비

개발한 앱을 배포하기 위한 준비 단계를 알아봅시다.

앱 아이콘 설정 🎨

  1. 앱 아이콘 생성:
    • File > New > Image Asset
    • 소스 이미지 선택 (PNG, SVG)
    • 아이콘 유형 선택 (Launcher Icons, Adaptive Icons)
    • 배경색, 전경 이미지 설정
    • Next > Finish
  2. 다양한 해상도 대응:
    • 자동으로 생성되는 다양한 크기(mdpi, hdpi, xhdpi, xxhdpi, xxxhdpi)
    • res/mipmap-*/ 디렉토리에 생성됨
  3. Adaptive Icon 구조:
    • 배경(Background): mipmap-*/ic_launcher_background.png
    • 전경(Foreground): mipmap-*/ic_launcher_foreground.png
    • XML 파일: mipmap-anydpi-v26/ic_launcher.xml

앱 서명 및 릴리스 빌드 🔏

  1. KeyStore 생성:
    • Build > Generate Signed Bundle / APK
    • APK 선택 > Next
    • Create new... 클릭
    • 키 정보 입력 (위치, 비밀번호, 별칭, 유효기간 등)
    • KeyStore 파일 (.jks 또는 .keystore) 저장
  2. 릴리스 빌드 구성:
    • Build > Generate Signed Bundle / APK
    • KeyStore 정보 입력
    • release 빌드 유형 선택
    • Finish 클릭
  3. ProGuard 설정 (코드 축소 및 난독화):
  4. android { buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } }

Google Play 스토어 게시 🏪

  1. Google Play Console 계정:
    • Google Play Console 접속
    • Google 계정으로 로그인
    • 개발자 등록 (등록비 $25, 일회성)
  2. 앱 정보 준비:
    • 앱 이름 및 간단한 설명
    • 자세한 설명 (최대 4,000자)
    • 앱 카테고리 선택
    • 이메일 주소
    • 개인정보처리방침 URL
  3. 시각 자료 준비:
    • 아이콘 (512x512px)
    • 피처드 그래픽 (1024x500px)
    • 스크린샷 (최소 2장, 다양한 디바이스)
    • 프로모션 비디오 (선택사항)
  4. APK/AAB 업로드:
    • AAB(Android App Bundle) 권장
    • 버전 코드와 버전 이름 확인
    • Alpha/Beta 테스트 또는 바로 출시
  5. 콘텐츠 등급 작성:
    • 앱 콘텐츠 유형에 대한 질문지 작성
    • 적합한 연령 등급 획득
  6. 가격 책정 및 배포:
    • 무료/유료 선택
    • 배포 국가 선택
    • 출시 승인 대기 (보통 몇 시간~며칠)

개발자 유형별 학습 로드맵

개발 경험에 따른 맞춤형 Android 개발 학습 경로입니다.

👶 초보 개발자 로드맵

  1. 프로그래밍 기초 (1-2개월)
    • Kotlin 기본 문법 익히기
    • 객체지향 프로그래밍 개념 이해
    • 간단한 알고리즘 문제 풀이
  2. Android 기초 (2-3개월)
    • 액티비티와 생명주기 이해
    • 기본 UI 구성요소 활용
    • 간단한 이벤트 처리
    • 간단한 앱 만들기 (계산기, ToDo 리스트)
  3. 기본 앱 개발 (3-4개월)
    • 다중 화면 구현
    • 간단한 데이터 저장 (SharedPreferences)
    • RecyclerView 활용한 목록 표시
    • 기본 네트워크 통신

👨‍💻 중급 개발자 로드맵

  1. 고급 UI 개발 (2-3개월)
    • ConstraintLayout 마스터
    • Material Design 적용
    • 애니메이션과 전환 효과
    • 커스텀 뷰 개발
  2. 데이터 관리 (2-3개월)
    • Room 데이터베이스 활용
    • LiveData와 ViewModel
    • 코루틴 활용한 비동기 처리
    • Repository 패턴 적용
  3. 아키텍처 패턴 (3-4개월)
    • MVVM 아키텍처 적용
    • Clean Architecture 이해
    • Navigation Component 활용
    • 의존성 주입 (Hilt/Dagger)

🧙‍♂️ 시니어 개발자 로드맵

  1. 고급 아키텍처 (3-4개월)
    • 모듈화된 앱 구조 설계
    • 멀티모듈 프로젝트 구성
    • 테스트 주도 개발(TDD)
    • Jetpack Compose UI 전환
  2. 성능 최적화 (2-3개월)
    • 메모리 사용량 최적화
    • UI 렌더링 성능 향상
    • 배터리 효율성 개선
    • APK 크기 최소화
  3. CI/CD 및 DevOps (2-3개월)
    • 자동화된 빌드 및 테스트
    • Github Actions 활용
    • 다양한 환경(Flavor) 설정
    • 앱 모니터링 및 분석

마무리 및 다음 단계

Kotlin과 Android Studio를 활용한 기본적인 모바일 앱 개발 방법을 배웠습니다. 이제 여러분은 간단한 앱을 만들 수 있는 능력을 갖추게 되었으며, 앞으로 더 많은 것을 배울 준비가 되었습니다.

배운 내용 요약 📝

  • Kotlin 언어의 기본 문법과 특징
  • Android Studio 설정 및 사용법
  • Android 앱의 기본 구성 요소
  • UI 디자인 및 레이아웃 구성
  • 사용자 이벤트 처리 및 화면 전환
  • 데이터 저장 및 네트워크 통신
  • 디버깅 및 테스트 방법
  • 앱 배포 준비 단계

추천 학습 자료 📚

  1. 도서
    • "Kotlin in Action" by Dmitry Jemerov and Svetlana Isakova
    • "Android Programming: The Big Nerd Ranch Guide"
    • "Head First Android Development"
  2. 온라인 코스
  3. 커뮤니티 및 리소스

다음 단계로 추천하는 주제 🚀

  1. Jetpack 컴포넌트 깊이 있게 배우기
    • ViewModel, LiveData, DataBinding
    • Navigation Component
    • WorkManager
    • Paging Library
  2. 현대적인 UI 개발
    • Jetpack Compose (선언적 UI 프레임워크)
    • Motion Layout (고급 애니메이션)
    • Material Design 3 적용
  3. 아키텍처 패턴 적용
    • MVVM (Model-View-ViewModel)
    • Clean Architecture
    • MVI (Model-View-Intent)
  4. Flutter 탐험하기
    • 크로스 플랫폼 개발 (iOS + Android)
    • Dart 언어 학습
    • 기존 Android 지식과 비교
  5. Firebase 서비스 통합
    • Firebase Authentication
    • Cloud Firestore
    • Firebase Analytics
    • Firebase Cloud Messaging

Android 앱 개발 여정은 이제 막 시작되었습니다! 지속적인 학습과 실습을 통해 실력을 향상시켜 나가세요. 질문이나 피드백이 있으시면 언제든지 댓글로 남겨주세요. 행복한 코딩되세요! 🚀