프로젝트: Lemonade 앱

이 Codelab에서는 개발자가 직접 빌드할 Lemonade라는 새로운 앱을 소개합니다. 이 Codelab에서는 Android 스튜디오 내에서의 설정 및 테스트를 비롯하여 프로젝트를 완료하는 단계를 안내합니다.

이 Codelab은 이 과정의 다른 Codelab과 다릅니다. 이전 Codelab과 달리 이 Codelab의 목적은 앱을 빌드하는 방법에 관한 단계별 튜토리얼을 제공하는 것이 아닙니다. 대신 이 Codelab은 개발자가 독립적으로 완료할 프로젝트를 설정하기 위한 것으로, 앱을 완성하고 직접 작업을 확인하는 방법에 관한 안내를 제공합니다.

솔루션 코드 대신 Google에서는 다운로드할 앱의 일부로 테스트 모음을 제공합니다. Android 스튜디오에서 이러한 테스트를 실행하고(이 Codelab 뒷부분에서 실행 방법을 설명함) 코드가 통과하는지 확인합니다. 여러 번 시도해야 할 수 있습니다. 전문 개발자도 첫 번째 시도에서 모든 테스트에 통과하는 경우는 거의 없습니다. 코드가 모든 테스트에 통과하면 이 프로젝트를 완료된 것으로 간주할 수 있습니다.

대조하여 확인할 솔루션 코드만 원할 수 있음을 알고 있습니다. Google에서는 의도적으로 솔루션 코드를 제공하지 않습니다. 전문 개발자가 되는 것이 어떤 것인지 실제로 경험하길 바라기 때문입니다. 그러려면 다음과 같이 아직 연습을 많이 하지 않은 다양한 기술을 사용해야 할 수 있습니다.

  • 알 수 없는 앱의 용어와 오류 메시지, 코드를 Google에서 검색합니다.
  • 코드를 테스트하고 오류를 읽은 후 코드를 변경하고 다시 코드를 테스트합니다.
  • Android 기본사항 1단원의 이전 콘텐츠로 돌아가 학습한 내용을 새로고침합니다.
  • 알고 있는 작동하는 코드(즉, 프로젝트에 제공된 코드나 1단원에 있는 다른 앱의 이전 솔루션 코드)를 작성하고 있는 코드와 비교합니다.

처음에는 이 작업이 어렵게 보일 수도 있지만 1단원을 완료할 수 있다면 이 프로젝트를 진행할 준비가 되었다고 100% 확신합니다. 천천히 진행하면서 끝까지 포기하지 마세요. 할 수 있습니다.

기본 요건

  • 이 프로젝트는 Kotlin의 Android 기본사항 과정의 1단원을 완료한 사용자를 대상으로 합니다.

빌드할 항목

  • 1단원에서 배운 기술을 사용하여 간단한 Lemonade 앱을 빌드합니다.

필요한 항목

  • Android 스튜디오가 설치된 컴퓨터

프로젝트: Lemonade 앱에 오신 것을 환영합니다.

디지털 레모네이드를 만드는 비전을 실현할 수 있도록 여러분을 팀의 일원으로 선택했습니다. 목표는 레모네이드 한 잔이 나올 때까지 레몬의 즙을 짤 수 있는 간단한 대화형 모바일 앱을 만드는 것입니다. 은유적 표현으로 생각하거나 시간을 보내는 재미있는 방법이라고 생각하세요.

완성된 Lemonade 앱은 단일 화면으로 구성됩니다. 사용자가 처음 앱을 실행하면 레몬 나무 그림을 탭하여 레몬을 선택하라는 메시지가 표시됩니다.

d61077d38ce788da.png

레몬 나무를 탭하면 다음 화면으로 이동하기 전에 사용자가 탭하여 지정되지 않은 횟수(정확하게 필요한 압착 횟수는 무작위로 생성됨)로 '압착'할 수 있는 레몬이 표시됩니다.

c4897dc7432c2908.png

사용자가 레몬을 탭하여 정확한 횟수로 압착하면 레모네이드를 마시기 위한 잔 이미지가 표시됩니다.

846dd7e1b8c230c.png

레모네이드를 클릭하여 마시면 잔이 빈 상태로 표시되고 사용자는 다시 이미지를 탭하여 첫 화면으로 돌아가 나무에서 다른 레몬을 선택할 수 있습니다.

9dfc622d3c2efee7.png

이 앱은 단순성을 중심으로 빌드되고 단일 활동으로 구성됩니다. 여러 앱 상태(사용자의 레몬 선택, 레몬 압착, 레모네이드 마시기, 마지막으로 빈 잔)는 상태 머신으로 표시됩니다. 복잡한 이론적 용어처럼 들릴 수 있지만 앱 상태(즉, 사용자에게 표시되는 텍스트와 이미지)는 앱 상태(select, squeeze 등)가 포함된 변수로 결정될 뿐입니다. 필요한 다른 변수와 함께 앱 상태가 업데이트되면 모든 업데이트가 완료된 후 UI가 별도로 구성(이미지와 텍스트 설정)됩니다.

앱 상태 변수는 모두 정의되어 있습니다. 여러분이 할 일은 UI가 각 상태 간에 예상대로 전환되도록 앱 레이아웃을 빌드하고 로직을 구현하는 것입니다.

코드 테스트

Lemonade 앱과 향후 프로젝트의 경우 코드가 예상대로 작동하는지 확인하는 데 사용할 수 있는 자동화된 테스트가 제공됩니다.

자동화된 테스트란 정확히 무엇인가요? 소프트웨어 개발에서는 '테스트'를 다른 코드가 작동하는지 확인하는 코드로 생각하면 됩니다. 이는 출력(예: 화면의 UI 요소 콘텐츠)을 확인하여 '테스트 사례'라는 입력에 기반해 적절한지 확인하는 방식으로 실행됩니다. Lemonade 앱의 시작 프로젝트에는 로직이 올바르게 구현되었는지 확인하는 데 실행할 수 있는 몇 가지 테스트가 포함되어 있습니다. 테스트에 관해서는 나중에 자세히 다룹니다. 지금은 시작 코드를 다운로드하고 Lemonade 앱을 빌드해보겠습니다.

프로젝트 코드 다운로드

폴더 이름은 android-basics-kotlin-lemonade-app입니다. Android 스튜디오에서 프로젝트를 열 때 이 폴더를 선택하세요.

이 Codelab의 코드를 가져와서 Android 스튜디오에서 열려면 다음을 실행합니다.

코드 가져오기

  1. 제공된 URL을 클릭합니다. 브라우저에서 프로젝트의 GitHub 페이지가 열립니다.
  2. 프로젝트의 GitHub 페이지에서 Code 버튼을 클릭하여 대화상자를 엽니다.

5b0a76c50478a73f.png

  1. 대화상자에서 Download ZIP 버튼을 클릭하여 컴퓨터에 프로젝트를 저장합니다. 다운로드가 완료될 때까지 기다립니다.
  2. 컴퓨터에서 파일을 찾습니다(예: Downloads 폴더).
  3. ZIP 파일을 더블클릭하여 압축을 해제합니다. 프로젝트 파일이 포함된 새 폴더가 만들어집니다.

Android 스튜디오에서 프로젝트 열기

  1. Android 스튜디오를 시작합니다.
  2. Welcome to Android Studio 창에서 Open an existing Android Studio project를 클릭합니다.

36cc44fcf0f89a1d.png

참고: Android 스튜디오가 이미 열려 있는 경우 File > New > Import Project 메뉴 옵션을 대신 선택합니다.

21f3eec988dcfbe9.png

  1. Import Project 대화상자에서 압축 해제된 프로젝트 폴더가 있는 위치로 이동합니다(예: Downloads 폴더).
  2. 프로젝트 폴더를 더블클릭합니다.
  3. Android 스튜디오가 프로젝트를 열 때까지 기다립니다.
  4. Run 버튼 11c34fc5e516fb1c.png을 클릭하여 앱을 빌드하고 실행합니다. 예상대로 작동하는지 확인합니다.
  5. Project 도구 창에서 프로젝트 파일을 살펴보고 앱이 설정된 방식을 확인합니다.

잠시 시간을 내어 시작 프로젝트를 숙지하세요. 특히 MainActivity.kt 파일에 유의하세요.

187e261ddee1b032.png

MainActivity.kt에서 앱의 현재 상태를 나타내는 데 사용되는 여러 변수를 확인할 수 있습니다. 이후 단계에서 이러한 변수를 사용하여 앱을 대화형으로 만듭니다. 여기서 코드 양이 너무 많은 것처럼 보일 수 있지만 TODO로 표시되지 않은 코드는 수정하지 않아도 됩니다. 다음 페이지에서 자세하게 안내합니다.

프로젝트에는 com.example.lemonade(androidTest)라는 다른 패키지도 포함되어 있습니다.

d68e0812be53370d.png

여기에는 MainActivity.kt에서 구현한 기능이 올바른지 확인하는 데 사용할 자동화된 테스트가 포함되어 있습니다. 이 내용도 나중에 자세하게 다룹니다. 이제 사용자 인터페이스부터 앱을 빌드할 준비가 되었습니다.

Lemonade 앱에는 기본 레이아웃만 있으면 됩니다. 모든 기능을 구현하려면 뷰가 두 개면 됩니다.

  1. 사용자에게 안내를 제공하는 TextView
  2. 앱의 현재 상태(예: 압착할 레몬)에 따라 그래픽이 표시되는 ImageView

이 레이아웃은 activity_main.xml에서 빌드합니다.

da9d7b9e9e85b339.png

Layout Editor에 관한 지식을 바탕으로 다음과 같이 ImageView 위에 TextView가 화면 중앙에 위치하는 레이아웃을 빌드하는 것이 목표입니다.

d384c5cc4d7b5634.png

레이아웃이 완료되면 MainActivity.kt를 엽니다. 여기에서 앱의 로직을 모두 구현합니다. 이미 코드가 상당히 많이 있습니다. // TODO:로 표시된 주석(아래 예 참고)도 많습니다. 다음 작업을 완료해야 합니다.

d3a4d509918df93c.png

세 가지 기본 작업을 구현해야 레모네이드 앱이 작동합니다.

  1. 사용자 입력에 응답하도록 lemonImage ImageView를 구성합니다.
  2. clickLemonImage()를 구현하여 앱의 상태를 업데이트합니다.
  3. setViewElements()를 구현하여 앱의 현재 상태에 따라 UI를 업데이트합니다.

한 번에 하나씩 각 작업을 살펴보겠습니다.

1단계: ImageView 구성

이미지 뷰를 탭하면 앱이 한 상태에서 다른 상태로 이동해야 합니다. onCreate() 끝에는 설정해야 하는 리스너가 두 개 있습니다.

  1. setOnClickListener()는 앱의 상태를 업데이트해야 합니다. clickLemonImage() 메서드를 사용하면 됩니다.
  2. setOnLongClickListener()는 사용자가 이미지를 길게 누르는 이벤트(예: 사용자가 이미지를 탭하고 곧바로 손가락을 떼지 않는 동작)에 응답합니다. 길게 누르기 이벤트의 경우 스낵바라는 위젯이 화면 하단에 표시되어 사용자에게 레몬을 압착한 횟수를 알려줍니다. 이 작업은 showSnackbar() 메서드를 통해 실행됩니다.

54f9f0a3aa903c31.png

다음 단계에서는 앱 상태를 변경하는 로직을 구현합니다.

2단계: clickLemonImage() 구현

이전 단계를 완료하면 이제 사용자가 이미지를 탭할 때마다 clickLemonImage() 메서드가 호출됩니다. 이 메서드는 앱을 현재 상태에서 다음 상태로 이동하고 필요에 따라 변수를 업데이트합니다. 가능한 네 가지 상태는 SELECT, SQUEEZE, DRINK, RESTART입니다. 현재 상태는 lemonadeState 변수로 나타냅니다. 이 메서드는 상태별로 다른 작업을 해야 합니다.

  1. SELECT: SQUEEZE 상태로 전환되고 pick() 메서드를 호출하고 squeezeCount(사용자가 레몬을 압착한 횟수)를 0으로 설정하여 lemonSize(필요한 압착 횟수)를 설정합니다.
  2. SQUEEZE: squeezeCount를 1씩 늘리고 lemonSize를 1씩 줄입니다. 레몬은 squeezeCount 전에 압착 횟수가 가변적이어야 합니다. 새 lemonSize가 0인 경우에만 DRINK 상태로 전환됩니다. 0이 아니면 앱은 SQUEEZE 상태로 유지되어야 합니다.
  3. DRINK: RESTART 상태로 전환되고 lemonSize를 -1로 설정합니다.
  4. RESTART: SELECT 상태로 다시 전환됩니다.

상태 간의 모든 업데이트와 전환을 처리했으면 setViewElements()를 호출하여 새 상태에 따라 UI를 업데이트해야 합니다.

3단계: setViewElements() 구현

setViewElements() 메서드는 앱 상태에 따라 UI를 업데이트합니다. lemonadeState와 일치하도록 아래에 나온 값으로 텍스트와 이미지를 업데이트해야 합니다.

SELECT

  • 텍스트: 레몬을 클릭하여 선택합니다.
  • 이미지: R.drawable.lemon_tree

SQUEEZE

  • 텍스트: 레몬을 클릭하여 즙을 냅니다.
  • 이미지: R.drawable.lemon_squeeze

DRINK

  • 텍스트: 레모네이드를 클릭하여 마십니다.
  • 이미지: R.drawable.lemon_drink

RESTART

  • 텍스트: 클릭하여 다시 시작합니다.
  • 이미지: R.drawable.lemon_restart

문자열 리소스 사용 방법

Android에서는 거의 모든 것이 리소스입니다. 따라서 앱에서 액세스할 수 있는 리소스를 정의하는 것이 Android 개발의 필수 요소입니다.

리소스는 색상, 이미지, 레이아웃, 메뉴, 문자열 값을 정의하는 것부터 모든 것에 사용됩니다. 이 경우 이점은 어떤 것도 하드코딩되지 않는다는 점입니다. 모든 것이 이러한 리소스 파일에서 정의되고 애플리케이션의 코드 내에서 참조될 수 있습니다. 이러한 리소스 중 가장 간단하고 가장 일반적인 것은 유연하고 현지화된 텍스트를 허용하는 문자열 리소스를 사용하는 것입니다.

문자열 또는 정적 텍스트는 res 폴더의 values 하위 폴더에 strings.xml이라는 별도의 파일로 저장할 수 있습니다.

65588c6102e5139.png

애플리케이션 내에 표시하려는 모든 텍스트(버튼 라벨이나 TextView 내부 텍스트)의 경우 res/values/strings.xml 파일에서 먼저 텍스트를 정의해야 합니다. 각 항목은 키(텍스트 ID를 나타냄)와 값(텍스트 자체)입니다. 예를 들어 버튼에 '제출'이 표시되도록 하려면 다음 문자열 리소스를 res/values/strings.xml에 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="hello">Hello!</string>
    <string name="submit_label">Submit</string>
</resources>

코드에서 리소스에 직접 액세스하려면 getResources.getString() 또는 getString() 메서드를 사용하여 리소스 ID R.string.submit_label이 지정된 값에 액세스하면 됩니다.

val submitText = getResources().getString(R.string.submit_label)

문자열 리소스의 텍스트를 TextView에 직접 설정하려면 TextView 객체에서 setText()를 호출하고 리소스 ID를 전달하면 됩니다.

val infoTextView: TextView = findViewById(R.id.info_textview)

infoTextView.setText(R.string.info_text)

문자열 리소스에는 텍스트 형식 지정을 위한 특수문자도 포함될 수 있습니다. 예를 들어 문자열에 다른 텍스트를 삽입할 수 있는 문자열 리소스가 있을 수 있습니다.

<string name="ingredient_tablespoon">%1$d tbsp of %2$s</string>

코드에서 인수를 전달하여 문자열 리소스에 액세스하고 형식을 지정합니다.

getResources().getString(R.string.ingredient_tablespoon, 2, "cocoa powder")

문자열 리소스를 선언할 때 각 인수는 표시되는 순서로 번호가 지정되고(1, 2 등) 유형을 식별하는 문자가 있습니다(십진수는 d, 문자열은 s 등). 올바른 유형의 인수는 getString() 호출에 전달할 수 있습니다.

2 tbsp of cocoa powder

자세한 내용은 문자열 리소스에 관한 문서를 참고하세요.

앱 UI를 빌드하고 기본 활동을 구현했다면 이제 실제로 어떻게 작동하는지 살펴보겠습니다. Run > Run 'app' 메뉴를 사용하여 앱을 실행하면 에뮬레이터가 실행됩니다.

ae484ede4fe1dd1f.png

이제 앱이 대화형으로 완전히 작동하고 이미지 뷰를 탭하여 상태 간에 전환할 수 있어야 합니다.

3e8867f3aea54c78.png

레몬이 화면에 표시되는 동안 ImageView를 길게 눌러 레몬이 압착된 총횟수를 나타내는 하단의 스낵바를 확인할 수도 있습니다. 잠시 시간을 내어 모든 앱 상태를 여러 번 살펴보세요. 수고하셨습니다. 앱 구현을 완료했습니다.

앱 테스트

Lemonade 앱 구현을 완료했지만 전문 소프트웨어 개발에서 코드만 작성하는 것으로 단계가 마무리되는 경우는 거의 없습니다. 애플리케이션 코드 외에도 전문 품질 앱에는 코드가 예상대로 작동하고 코드 변경사항으로 새로운 버그가 발생하지 않는지 확인(이 프로세스를 자동화된 테스트라고 함)하는 데 실행되는 테스트 코드도 포함되어 있습니다. 자동화된 테스트 학습은 이 프로젝트의 범위를 벗어나지만 Lemonade 앱은 프로젝트를 올바르게 구현했는지 확인할 수 있는 몇 가지 테스트와 함께 제공됩니다. 이를 자체 채점의 형태로 사용하여 모든 프로젝트 요구사항을 충족했는지 앱에 변경할 부분이 있는지 확인할 수 있습니다.

'테스트'란 정확히 무엇인가요? 테스트는 앱 코드의 일부를 실행하는 Android 스튜디오 프로젝트에 포함된 코드 조각으로, 앱 코드가 예상대로 동작하는지에 따라 테스트에 '통과'하거나 '실패'할 수 있습니다.

그러면 어디에서 앱 테스트를 찾아 실행하나요? Lemonade 앱 테스트는 테스트 타겟에서 확인할 수 있습니다. 타겟은 함께 번들로 제공되는 클래스 모음의 소프트웨어 개발 용어입니다. 예를 들어 Lemonade 앱은 '앱'이라는 타겟에 있지만 테스트는 'LemonadeTests'라는 타겟에 있습니다. LemonadeTests 타겟은 앱 타겟의 코드에 액세스할 수 있지만 완전히 별개이며 앱 코드에는 테스트 코드가 포함되어 있지 않습니다.

20edcb90dca9d70b.png

'Android' 뷰에서 파일을 볼 때 테스트 타겟은 앱과 같은 패키지 이름으로 표시되지만 (androidTest)는 괄호로 표시됩니다.

테스트 코드를 참조할 때 알아야 할 주요 용어도 있습니다.

  • 테스트 모음 - 모든 테스트 사례가 포함된 타겟입니다.
  • 테스트 사례 - 관련 기능의 개별 테스트로 구성된 클래스입니다. Lemonade 앱에는 테스트 사례가 하나뿐이지만 더 큰 앱에는 대부분 테스트 사례가 많습니다.
  • 테스트 - 한 가지 특정 항목을 테스트하는 함수입니다.

테스트 사례에는 테스트가 여러 개일 수 있고 프로젝트의 테스트 모음에는 테스트 사례가 여러 개일 수 있습니다.

테스트 실행

테스트를 실행하려면 다음 중 하나를 실행하면 됩니다.

단일 테스트 사례의 경우 테스트 사례 클래스를 열고 클래스 선언 왼쪽의 녹색 화살표를 클릭합니다. 그런 다음 메뉴에서 실행 옵션을 선택하면 됩니다. 그러면 테스트 사례의 테스트가 모두 실행됩니다.

a32317d35c77142b.png

예를 들어 실패한 테스트는 하나뿐이고 나머지 테스트는 통과된 경우와 같이 단일 테스트만 실행하려는 때가 많습니다. 전체 테스트 사례와 마찬가지로 단일 테스트를 실행할 수 있습니다. 녹색 화살표를 사용하여 실행 옵션을 선택합니다.

ac6244434cfafb60.png

테스트 사례가 여러 개라면 전체 테스트 모음을 실행할 수도 있습니다. 앱을 실행하는 것과 마찬가지로 Run 메뉴에서 이 옵션을 찾을 수 있습니다.

7a925c5e196725bb.png

Android 스튜디오는 마지막으로 실행한 타겟(앱, 테스트 타겟 등)을 기본값으로 설정하므로 메뉴에 여전히 Run > Run 'app'이라고 표시되면 Run > Run을 선택하여 테스트 타겟을 실행할 수 있습니다.

ee1e227446c536fe.png

그런 다음 팝업 메뉴에서 테스트 타겟을 선택합니다.

d570c947769db65c.png

테스트 실행 결과는 Run 탭에 표시됩니다. 왼쪽 창에서 실패한 테스트 목록을 확인할 수 있습니다(있는 경우). 실패한 테스트는 함수 이름 옆에 빨간색 느낌표가 표시됩니다. 통과한 테스트는 녹색 체크표시가 표시됩니다.

6d68f2bf589501ae.png

테스트에 실패하면 텍스트 출력을 통해 테스트 실패를 야기한 문제를 해결할 수 있는 정보가 제공됩니다.

92f3c8219c03651d.png

예를 들어 위 오류 메시지에서 테스트는 TextView가 특정 문자열 리소스를 사용하고 있는지 확인합니다. 그러나 테스트에 실패합니다. 'Expected'와 'Got' 다음에 있는 텍스트가 일치하지 않습니다. 즉, 테스트에서 예상한 값이 실행되는 앱의 값과 일치하지 않습니다. 이 예에서 TextView에 사용된 문자열은 테스트에서 예상한 대로 실제로 squeeze_count가 아닙니다.

레모네이드를 여러 잔 즐기고 난 후 가장 좋아하는 화면의 스크린샷을 찍고 Twitter에 공유하여 배운 내용을 보여주세요. @AndroidDev를 태그하고 #AndroidBasics 해시태그를 추가합니다.