AWS 시작하기
Android 애플리케이션 구축
AWS Amplify를 사용하여 간단한 Android 애플리케이션 만들기
모듈 1: Android 앱 생성 및 배포
이 모듈에서는 AWS Amplify의 웹 호스팅 서비스를 사용하여 Android 애플리케이션을 생성하고 클라우드에 배포합니다.
소개
AWS Amplify는 웹 및 모바일 애플리케이션에 대한 서버리스 백엔드의 생성, 관리, 통합 및 배포를 위한 Git 기반 워크플로를 제공합니다. Amplify CLI는 사용자 인증 또는 REST 또는 GraphQL API와 같은 애플리케이션 백엔드 서비스를 프로비저닝하고 관리할 수 있는 단순한 텍스트 기반 사용자 인터페이스를 제공합니다. Amplify 라이브러리는 이러한 백엔드 서비스를 코드 몇 줄로 손쉽게 애플리케이션에 통합하는 데 사용됩니다.
이 모듈에서는 여행 메모를 기록하는 새 Android 애플리케이션을 생성합니다. 노트는 제목, 설명 및 사진으로 구성됩니다. 다음 모듈에서는 이 애플리케이션에 향상된 기능을 추가합니다.
배우게 될 내용
- Android 애플리케이션 만들기
- 기본 보기를 업데이트하여 항목 목록 표시
- 애플리케이션 구축 및 테스트
완료 시간
10분
사용되는 서비스
구현
-
Android 프로젝트 생성
Android Studio를 시작하고 스플래시 화면에서 [새 Android Studio 프로젝트 시작(Start a new Android Studio project)]을 선택합니다.
[Phone and Tablet(휴대폰 및 태블릿)]에서 [기본 작업(Basic Activity)]을 선택하고 [다음(Next)]을 클릭합니다.
프로젝트의 이름(예: Android Getting Started)을 입력합니다. 언어가 Kotlin이고 최소 SDK가 API 26: Android 8.0(oreo)인지 확인합니다. 그리고 [마침:(Finish:)]을 클릭합니다.
프로젝트의 골격이 준비되었으므로 기본 앱을 실행하려면 4단계가 필요합니다.
- 원하지 않는 클래스 및 파일을 삭제하고 플러그인을 추가합니다.
- 데이터 구조를 보유할 클래스를 만듭니다.
- 목록에서 개별 노트를 보관할 보기를 만듭니다.
- MainActivity를 수정하여 노트 목록을 표시합니다.
-
필요 없는 파일을 삭제하고 플러그인을 추가합니다.
res/layout 및 java/com.example.androidgetingstarted에서 4개의 조각 파일을 삭제합니다. 4개의 파일을 선택하고 마우스 오른쪽 버튼을 클릭한 후 컨텍스트 메뉴에서 [삭제(Delete)]를 선택합니다.
Gradle Scripts에서 build.gradle (Module:app)을 열고 Kotlin Extension 플러그인을 추가합니다.
plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-android-extensions' // <== add this line }
-
UserData 데이터 클래스를 생성합니다.
UserData 클래스는 사용자 상태, 즉 isSignedIn 플래그 및 Note 값 목록을 유지합니다.
새 클래스를 생성하려면 java/com.example/androidgettingstarted를 마우스 오른쪽 버튼으로 클릭하고 [새로 만들기(New)] -> [Kotlin 파일/클래스(Kotlin file/class)]를 선택합니다. 이름으로 UserData를 입력합니다.
방금 만든 새 파일(UserData.kt)에 아래 코드를 붙여 넣습니다.
package com.example.androidgettingstarted import android.graphics.Bitmap import android.util.Log import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData // a singleton to hold user data (this is a ViewModel pattern, without inheriting from ViewModel) object UserData { private const val TAG = "UserData" // // observable properties // // signed in status private val _isSignedIn = MutableLiveData<Boolean>(false) var isSignedIn: LiveData<Boolean> = _isSignedIn fun setSignedIn(newValue : Boolean) { // use postvalue() to make the assignation on the main (UI) thread _isSignedIn.postValue(newValue) } // the notes private val _notes = MutableLiveData<MutableList<Note>>(mutableListOf()) // please check https://stackoverflow.com/questions/47941537/notify-observer-when-item-is-added-to-list-of-livedata private fun <T> MutableLiveData<T>.notifyObserver() { this.postValue(this.value) } fun notifyObserver() { this._notes.notifyObserver() } fun notes() : LiveData<MutableList<Note>> = _notes fun addNote(n : Note) { val notes = _notes.value if (notes != null) { notes.add(n) _notes.notifyObserver() } else { Log.e(TAG, "addNote : note collection is null !!") } } fun deleteNote(at: Int) : Note? { val note = _notes.value?.removeAt(at) _notes.notifyObserver() return note } fun resetNotes() { this._notes.value?.clear() //used when signing out _notes.notifyObserver() } // a note data class data class Note(val id: String, val name: String, val description: String, var imageName: String? = null) { override fun toString(): String = name // bitmap image var image : Bitmap? = null } }
방금 추가한 것은 무엇입니까?
- UserData 클래스는 사용자 데이터, 즉 현재 인증 상태 및 Note 객체 목록을 추적하는 isSignedIn 플래그를 저장하는 역할을 합니다.
- 이 두 속성은 LiveData 게시/구독 프레임워크에 따라 구현됩니다. GUI(그래픽 사용자 인터페이스)를 통해 변경 사항을 구독하고 그에 따라 대응할 수 있습니다. LiveData에 대해 자세히 알아보려면 이 문서를 읽거나 이 짧은 비디오 자습서를 따르면 됩니다. 모범 사례를 따르려면 MutableLiveData 속성을 비공개로 유지하고 읽기 전용 LiveData 속성만 노출합니다. 게시할 데이터가 목록인 경우 목록의 개별 구성 요소가 수정될 때 옵저버에 알리도록 하려면 몇 가지 추가 상용 코드가 필요합니다.
- 개별 노트의 데이터를 저장하기 위해 Note 데이터 클래스도 추가했습니다. ImageName 및 Image에는 두 가지 고유한 속성이 사용됩니다. 이미지는 후속 모듈에서 처리됩니다.
- UserData만으로 애플리케이션 내의 어느 위치에서나 참조할 수 있도록 UserData 객체에 대해 싱글톤 설계 패턴을 구현했습니다.
-
목록에서 개별 셀에 대한 GUI 추가
화면에 더 이상 보기를 표시할 수 없는 경우 사용자가 위아래로 스크롤할 때 보기를 재활용할 수 있으므로, 스크롤 목록의 개별 셀을 RecyclerView라고 합니다.
일반 보기와 마찬가지로 레이아웃 XML 파일과 Kotlin 클래스를 생성합니다. 개별 셀은 다음과 같습니다.
레이아웃 파일을 생성하려면 res/layout을 마우스 오른쪽 버튼으로 클릭하고 [새로 만들기(New)] -> [레이아웃 리소스 파일(Layout Resource File)]을 선택합니다. content_note를 이름으로 입력하고, XML 파일을 직접 편집할 것이므로 다른 모든 값은 그대로 둡니다.
새로 생성한 파일에 대해 코드 보기를 엽니다.
방금 생성한 새 파일의 코드 보기(content_note.xml)에서 아래 코드를 붙여 넣어 생성한 코드를 바꿉니다.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingVertical="16dp"> <ImageView android:id="@+id/image" android:layout_width="100dp" android:layout_height="match_parent" android:padding="8dp" android:scaleType="centerCrop" android:src="@drawable/ic_launcher_background" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="5dp" android:orientation="vertical"> <TextView android:id="@+id/name" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Title" android:textSize="20sp" android:textStyle="bold" /> <TextView android:id="@+id/description" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Title" android:textSize="15sp" /> </LinearLayout> </LinearLayout>
마지막으로 Kotlin 클래스를 생성합니다. java/com.example/androidgettingstarted를 마우스 오른쪽 버튼으로 클릭하고 [새로 만들기(New)] -> [Kotlin 파일/클래스(Kotlin file/class)]를 선택합니다. 이름으로 NoteRecyclerViewAdapter를 입력합니다.
방금 생성한 새 파일(NoteRecyclerViewAdapter.kt)에 아래 코드를 붙여넣 습니다.
package com.example.androidgettingstarted import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.RecyclerView // this is a single cell (row) in the list of Notes class NoteRecyclerViewAdapter( private val values: MutableList<UserData.Note>?) : RecyclerView.Adapter<NoteRecyclerViewAdapter.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.content_note, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val item = values?.get(position) holder.nameView.text = item?.name holder.descriptionView.text = item?.description if (item?.image != null) { holder.imageView.setImageBitmap(item.image) } } override fun getItemCount() = values?.size ?: 0 inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { val imageView: ImageView = view.findViewById(R.id.image) val nameView: TextView = view.findViewById(R.id.name) val descriptionView: TextView = view.findViewById(R.id.description) } }
방금 추가한 것은 무엇입니까?
위의 코드는 다음으로 구성됩니다.- 노트를 나타내는 셀의 구성 요소 레이아웃을 설명하는 레이아웃 xml 파일. 이미지, 노트 제목 및 노트 설명을 표시합니다.
- 지원 Kotlin 클래스. 생성 시 Note 데이터 객체를 수신하고 해당 보기(이미지, 제목 및 설명)에 개별 값을 할당합니다.
-
기본 작업 업데이트
이제 데이터 클래스(UserData 및 Note)와 개별 노트의 보기(NoteRecyclerViewAdapter)를 생성했으므로 주요 작업에 대한 노트 목록을 만들어 보겠습니다.
Android Studio 왼쪽에 있는 파일 목록에서 res/layout/content_main.xml을 열고 코드를 다음 내용으로 바꿉니다.
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/frameLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <androidx.recyclerview.widget.RecyclerView android:id="@+id/item_list" android:name="com.example.myapplication.ItemListFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginTop="60dp" android:paddingHorizontal="8dp" android:paddingVertical="8dp" app:layoutManager="LinearLayoutManager" tools:context=".MainActivity" tools:listitem="@layout/content_note" /> </FrameLayout>
Android Studio 왼쪽에 있는 파일 목록에서 java/com.example/androidgettingstarted/MainActivity.kt를 열고 코드를 다음 콘텐츠로 바꿉니다.
package com.example.androidgettingstarted import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.content_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) // prepare our List view and RecyclerView (cells) setupRecyclerView(item_list) } // recycler view is the list of cells private fun setupRecyclerView(recyclerView: RecyclerView) { // update individual cell when the Note data are modified UserData.notes().observe(this, Observer<MutableList<UserData.Note>> { notes -> Log.d(TAG, "Note observer received ${notes.size} notes") // let's create a RecyclerViewAdapter that manages the individual cells recyclerView.adapter = NoteRecyclerViewAdapter(notes) }) } companion object { private const val TAG = "MainActivity" } }
방금 추가한 것은 무엇입니까?
- 기본 레이아웃은 이전에 생성한 개별 셀 목록을 관리하는 RecyclerView입니다.
- 기본 작업 클래스는 Notes 목록의 변경 사항을 관찰하고 개별 셀을 생성할 NoteRecyclerViewAdapter를 생성합니다.
-
빌드 종속성 확인
Gradle Scripts에서 build.gradle(Module:app)를 열고 생성된 종속 구성 요소가 올바른지 확인합니다. `libraries versions`를 확인해야 합니다.
```gradle dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.2.1' implementation 'androidx.constraintlayout:constraintlayout:2.0.2' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.1' implementation 'androidx.navigation:navigation-ui-ktx:2.3.1' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' }
-
구축 및 테스트
이제 시뮬레이터에서 앱을 구축하고 시작합니다. 도구 모음에서 [실행(Run)] 아이콘 ▶을 클릭하거나 ^ R을 입력합니다.
잠시 후 Android 시뮬레이터에서 앱이 시작되고 초기 빈 화면이 표시됩니다.
이 단계에서는 런타임에 렌더링할 데이터가 없습니다. 메일 아이콘은 나중에 제거하겠습니다.