Android 앱용 백엔드를 구축하는 방법은 무엇인가요?
이 글에서는 가장 인기 있는 운영 체제 중 하나인 Android에 대해 설명합니다. Android 개발의 장단점, 모바일 애플리케이션을 위한 백엔드 옵션을 살펴보고 자신만의 모바일 백엔드를 구축하는 방법을 알려드립니다.
Contents
주요 내용
- 안드로이드의 지배력: 오픈 소스 OS인 Android는 30억 명 이상의 활성 사용자를 보유한 모바일 디바이스의 약 70%를 구동합니다.
- 앱용 백엔드 옵션: 모바일 앱 백엔드의 경우 개발자는 서비스형 인프라(IaaS), 서비스형 플랫폼(PaaS) 또는 서비스형 백엔드(BaaS)를 선택할 수 있습니다.
- 백엔드 구축 단계 이 글의 마지막 부분에서 BaaS 플랫폼을 사용하여 안드로이드 백엔드를 만드는 방법에 대한 자세한 튜토리얼과 소스 코드를 제공합니다.
Android란 무엇인가요?
안드로이드는 무료 오픈소스 리눅스 기반 운영체제입니다. 주로 스마트폰과 태블릿과 같은 모바일 디바이스용으로 설계되었지만 현재는 스마트 TV, 임베디드 시스템, 게임 콘솔 등에도 사용됩니다.
Android의 개발은 2003년에 Android Inc. 처음에는 디지털 카메라용 OS를 개발하고자 했지만, 더 넓은 시장으로 진출하기 위해 빠르게 모바일 OS 개발로 방향을 전환했습니다.
2005년에 이 회사는 직원들과 함께 Google에 인수되었습니다. 안드로이드의 첫 번째 버전은 2008년에 출시되었습니다.
Android는 오픈 소스이지만 대부분의 모바일 디바이스에서는 Google의 독점 버전이 실행됩니다. 해당 버전은 Google Chrome, YouTube, Google TV, Gmail 등의 소프트웨어와 함께 사전 설치되어 제공됩니다.
Android의 뛰어난 사용자 지정 옵션으로 인해 많은 제조업체에서 회사를 더 잘 표현하기 위해 Android 스킨을 적용합니다. 이것이 원플러스 안드로이드가 픽셀 안드로이드와 상당히 다르게 보이는 이유입니다.
Android는 2013년부터 가장 인기 있는 운영체제입니다. 모바일 기기의 약 70%에서 사용되며 월간 활성 사용자 수가 30억 명이 넘습니다.
또한 Google Play 스토어에서는 300만 개 이상의 모바일 애플리케이션에 액세스할 수 있습니다. Android 앱용 백엔드를 만드는 방법을 알아보려면 계속 읽어보세요.
안드로이드 개발의 이점
Android용 개발의 몇 가지 이점을 살펴보세요.
크로스 플랫폼
Android용으로 개발하면 다양한 기기를 타겟팅할 수 있습니다. 여기에는 휴대폰, 웨어러블, 스마트 TV, 게임 콘솔 등이 포함됩니다. 모바일 앱을 코딩하는 방법을 알고 있다면 웨어러블 또는 스마트 TV 앱을 개발하는 데 아무런 문제가 없습니다.
거대한 시장
앞서 소개한 것처럼 Android의 시장 점유율은 70%로 iOS의 30%에 비해 더 높습니다. Android 앱을 개발하면 자동으로 더 많은 잠재 고객에게 도달할 수 있습니다.
게다가 안드로이드 앱은 투자 비용은 낮고 투자 수익률은 높은 것으로 알려져 있습니다. iOS와 달리 연간 개발자 수수료도 없습니다.
사용자 지정 기능
Android 기기는 다른 운영체제에 비해 사용자 지정이 매우 자유롭습니다. 상상할 수 있는 거의 모든 것을 변경할 수 있습니다.
또한 Android를 사용하면 앱을 다른 타사 서비스와 쉽게 통합할 수 있습니다.
커뮤니티
Android는 방대한 오픈소스 개발자 커뮤니티의 지원을 받습니다. 또한 앱을 쉽게 코딩할 수 있는 Android Studio, ADB, Logcat과 같은 개발자 친화적인 도구가 많이 제공됩니다.
문제가 발생하면 GitHub, StackOverflow, Reddit 및 기타 Android 중심 커뮤니티 등 여러 플랫폼에서 도움을 받을 수 있습니다.
안드로이드 개발의 한계
다음은 Android 개발의 몇 가지 단점입니다.
보안
Android는 iOS보다 보안성이 떨어지고 엄격한 보안 프로토콜을 따르지 않습니다. Android는 오픈 소스이기 때문에 매주 보안 취약점이 발견됩니다. 따라서 해커는 패치가 적용되기 전에 이러한 취약점을 악용할 수 있습니다.
Android 디바이스를 루팅하여 수퍼유저 액세스 권한을 얻을 수도 있습니다. 기기를 더 강력하게 만들 수 있지만 일부 기본 제공 보안 조치를 비활성화하므로 위험하기도 합니다.
복잡성
Android 기기는 다양한 형태와 크기로 제공되며, 이는 장점이지만 여러 기기 유형에 맞는 Android 앱을 개발할 때 큰 단점이 될 수 있습니다.
앱이 최대한 많은 디바이스와 호환되도록 하려면 반응형 디자인을 통합하고 다양한 디바이스의 하드웨어를 고려하는 등 여러 가지를 고려해야 합니다.
프리미엄 앱
프리미엄 앱은 앱스토어에 비해 구글 플레이 스토어 에서 성공률이 낮습니다. Android 사용자가 iOS 사용자보다 앱에 더 적은 비용을 지출하는 경향이 있다는 것은 잘 알려진 사실입니다. 프리미엄을 우선으로 하는 애플리케이션을 개발 중이라면 Android를 두 번째 선택지로 고려하세요.
Android 앱용 백엔드 옵션
Android 앱에 가장 적합한 백엔드는 무엇인가요? 모바일 애플리케이션용 백엔드는 자체 인프라에서 호스팅하거나 클라우드 서비스에 배포할 수 있습니다. 모바일 백엔드에 가장 많이 사용되는 클라우드 컴퓨팅 모델은 다음과 같습니다:
- 서비스형 인프라(IaaS)
- 서비스형 플랫폼(PaaS)
- 서비스형 백엔드(BaaS)
아래 이미지에 표시된 것처럼 각 옵션은 서로 다른 추상화 계층을 다룹니다.
백엔드 앱에 가장 적합한 클라우드 모델을 선택하는 데 도움이 되는 각 옵션을 자세히 살펴보세요.
서비스형 인프라(IaaS)
서비스형 인프라(IaaS)는 가장 덜 추상화된 클라우드 컴퓨팅 모델입니다. 이 모델에서 공급업체는 사용자에게 서버, 네트워킹, 스토리지, 운영 체제 등의 가상화된 리소스를 높은 수준의 API 또는 직관적인 대시보드를 통해 제공합니다.
IaaS의 가장 큰 장점은 사용자가 전체 인프라를 완벽하게 제어할 수 있다는 점입니다. IaaS는 가장 유연하고 확장성이 뛰어난 모델이지만 관리가 가장 어려운 모델이기도 합니다. 이 옵션을 선택하면 SysAdmin이 한두 명 필요할 가능성이 높습니다.
IaaS의 몇 가지 예로는 Amazon Web Services, Google Cloud Platform, Azure가 있습니다.
서비스형 플랫폼(PaaS)
서비스형 플랫폼(PaaS)은 사용자가 애플리케이션을 개발, 관리 및 배포할 수 있도록 설계된 클라우드 컴퓨팅 모델입니다.
인프라를 제공하는 것 외에도 PaaS에는 애플리케이션 개발, 사용자 지정 및 테스트를 위한 사용자 친화적인 도구가 함께 제공됩니다.
PaaS를 활용하면 기본 인프라에 대한 걱정 없이 소프트웨어와 사용자 경험에만 집중할 수 있습니다.
또한 PaaS는 필요에 따라 앱을 확장하고 백업을 처리하는 등의 기능을 제공합니다. PaaS의 단점은 제어 수준이 낮고 공급업체 종속의 위험이 있으며 상대적으로 비용이 높다는 점입니다.
가장 인기 있는 PaaS 플랫폼으로는 Heroku, Render, Digital Ocean 앱 플랫폼이 있습니다.
서비스형 백엔드(BaaS)
서비스형 백엔드(BaaS)는 완전한 백엔드 솔루션을 제공하여 개발의 백엔드 부분을 자동화합니다.
BaaS 기능에는 사용자 관리, 데이터베이스, 인증, 소셜 통합, 푸시 알림, API, SDK 등이 포함됩니다.
BaaS는 IaaS와 PaaS의 모든 장점에 추가 기능을 제공합니다. BaaS를 활용하는 팀은 제품을 더 빨리 출시하고 엔지니어링 비용을 절감하며 더 나은 소프트웨어를 구축하는 경향이 있습니다.
BaaS의 단점으로는 제어 및 사용자 지정 수준이 낮고 오픈 소스 플랫폼이 아닌 경우 공급업체에 종속될 가능성이 있다는 점이 있습니다.
BaaS의 예로는 Back4app, AWS Amplify, Firebase가 있습니다.
Android 앱용 백엔드를 만드는 방법은 무엇인가요?
이 문서 섹션에서는 Back4app 기반 백엔드 앱을 빌드하고 Android 애플리케이션에서 연결하는 방법을 살펴봅니다.
전제 조건
- Kotlin 및 Android 개발 경험.
- 서비스로서의 모바일 백엔드에 대한 기본적인 이해.
- 로컬 머신에 Java 및 Android Studio가 설치되어 있어야 합니다.
Back4app이란 무엇인가요?
Back4app은 최신 애플리케이션을 위한 백엔드를 구축하고 개발 프로세스를 가속화할 수 있는 훌륭한 플랫폼입니다.
사용자 관리, 실시간 데이터베이스, 소셜 통합, 클라우드 코드 기능, 푸시 알림, API 등과 같은 유용한 서버 측 기능이 잘 갖추어져 있습니다!
Back4app을 사용하면 백엔드 작업의 대부분을 오프소싱하고 애플리케이션의 중요한 측면에 집중하여 안드로이드 앱 백엔드 개발을 가속화할 수 있습니다.
백엔드의 기본 인프라, 백엔드 서버, 확장, 유지 관리 등을 처리할 필요가 없습니다.
가장 중요한 점은 Back4app은 플랫폼을 실험하고 테스트하기에 좋은 무료 티어를 제공한다는 점입니다. 앱이 성장하면 월정액으로 프리미엄 요금제로 업그레이드할 수 있습니다.
프로젝트 소개
이 글에서는 간단한 노트 앱을 만들어 보겠습니다. 이 앱을 통해 사용자는 메모를 추가, 편집, 삭제할 수 있습니다. 노트는 Back4app 데이터베이스에 저장되고 Parse SDK를 통해 조작됩니다.
앱 빌드에는 Kotlin 프로그래밍 언어를 사용합니다. UI에는 네이티브 UI를 빌드하기 위한 안드로이드의 최신 툴킷인 Jetpack Compose를 활용합니다.
백4앱 앱 만들기
Android 앱용 백엔드를 개발하기 위한 첫 번째 단계로 Back4app 계정이 필요합니다. 계정이 없는 경우 지금 바로 가입하세요.
Back4app 계정에 로그인하면 앱 보기로 리디렉션됩니다. “새 앱 만들기”를 클릭하여 앱 생성 프로세스를 초기화합니다.
Back4app 플랫폼은 두 가지 솔루션을 제공합니다:
- 서비스형 백엔드(BaaS) – 강력한 백엔드 솔루션
- 서비스형 컨테이너(CaaS) – 컨테이너(특히 웹 애플리케이션) 관리를 위한 플랫폼
모바일 앱용 백엔드를 구축한다는 점을 고려하여 ‘서비스형 백엔드’를 사용하겠습니다.
앱에 멋지고 유익한 이름을 지정하고 데이터베이스로 “NoSQL”을 선택한 다음 “만들기”를 클릭합니다. 이는 안드로이드 앱의 백엔드를 배포하는 중요한 단계입니다.
Back4app은 애플리케이션에 필요한 모든 것을 준비하는 데 시간이 조금 걸립니다. 여기에는 데이터베이스, 사용자 관리, 확장, 구성 등이 포함됩니다. 애플리케이션이 준비되면 앱의 데이터베이스 보기로 리디렉션됩니다.
데이터베이스 디자인
이제 데이터베이스를 디자인해 보겠습니다. 이는 안드로이드 앱의 백엔드를 개발하는 데 필요한 단계입니다.
간단한 노트 앱을 만들고 있으므로 클래스는 하나만 필요합니다. “클래스 만들기”를 클릭하고 이름을 노트(Note
)로 지정한 다음 “공개 읽기 및 쓰기”를 사용 설정하고 “클래스 만들기 및 열 추가”를 클릭합니다.
다음으로 새로 만든 클래스에 다음 세 개의 필드를 추가합니다:
+-------------+-------------+--------------------+----------+
| Data type | Name | Default value | Required |
+-------------+-------------+--------------------+----------+
| String | icon | <leave blank> | yes |
+-------------+-------------+--------------------+----------+
| String | title | <leave blank> | yes |
+-------------+-------------+--------------------+----------+
| String | content | <leave blank> | yes |
+-------------+-------------+--------------------+----------+
마지막으로 ‘행 추가’ 버튼을 클릭하고 몇 가지 샘플 데이터로 데이터베이스를 채웁니다. 아이디어가 떠오르지 않는다면 이 데이터 덤프를 가져올 수도 있습니다.
백엔드는 여기까지입니다.
코드 프론트엔드
이 글 섹션에서는 새 안드로이드 앱을 부트스트랩하고, 뷰모델을 설정하고, UI를 구현하고, Parse SDK를 설치 및 구성하고, 마지막으로 Back4app 실시간 데이터베이스에서 데이터를 가져오는 방법을 살펴봅니다.
프로젝트 초기화
사전 요구 사항 섹션에서 언급했듯이 다음 단계를 수행하려면 Android Studio가 설치되어 있어야 합니다. 아직 설치하지 않은 경우 다운로드하세요.
먼저 안드로이드 스튜디오를 열고 ‘새 프로젝트’ 버튼을 클릭합니다.
그런 다음 프로젝트 템플릿으로 “활동 비우기”를 선택하고 “다음”을 클릭합니다.
프로젝트를 만들려면 이름과 패키지 이름을 제공해야 합니다. 프로젝트의 이름으로 AndroidApp을
사용하고 프로젝트의 패키지로 역 도메인 이름을 사용하는 것이 좋습니다.
모든 설정을 마친 후 ‘마침’을 클릭하여 프로젝트를 생성합니다.
안드로이드 스튜디오에서 필요한 모든 것을 준비하는 데 약 2분이 소요됩니다. 여기에는 프로젝트 파일 구조 생성, Gradle 설정, 종속성 설치 등이 포함됩니다.
프로젝트가 준비되면 탐색기를 사용하여 MainActivity.kt로 이동하고 콘텐츠를 교체합니다:
// app/src/main/java.<your_package_name>/MainActivity.kt
package <your.package.name>
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
Text(text = "Back4app rocks!", fontSize = 18.sp)
}
}
}
}
}
를 항상 를 실제 패키지 이름으로 바꾸어야 합니다.
마지막으로 녹색 재생 버튼을 클릭하거나 키보드에서 Shift + F10을
눌러 앱을 실행해 보세요. 모든 것이 정상적으로 진행되면 에뮬레이터가 시작되고 화면에 Back4app rocks
메시지가 표시됩니다.
ViewModel
앱의 전역 상태를 관리하기 위해 뷰모델을
활용하겠습니다. 하지만 그 전에 백4앱 데이터베이스에 있는 것과 동일한 속성을 가진 노트
데이터 클래스를 만들어야 합니다.
Note라는
데이터 클래스를 만듭니다:
// app/src/main/java.<your_package_name>/Note.kt
package <your.package.name>
data class Note(
var objectId: String? = null,
var icon: String,
var title: String,
var content: String,
) {
companion object {
fun generateObjectId(): String {
val chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
return (1..10).map { chars.random() }.joinToString("")
}
}
}
- Parse와 유사한 객체 ID를 생성하기 위해 정적
generateObjectId()
메서드를 정의했습니다. - 객체가 아직 데이터베이스에 저장되지 않았다고 가정하기 때문에
objectId는
null이
될 수 있습니다.
다음으로 다음 내용으로 AppViewModel
클래스를 생성합니다:
// app/src/main/java.<your_package_name>/AppViewModel.kt
package <your.package.name>
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class AppViewModel : ViewModel() {
val notes: MutableState<Map<String, Note>> = mutableStateOf(mapOf(
"7IggsqFAKt" to Note(
objectId = "7IggsqFAKt",
icon = "\uD83D\uDE80",
title = "Deploy app",
content = "Go ahead and deploy your backend to Back4app.",
),
"eFRNm0hTat" to Note(
objectId = "eFRNm0hTat",
icon = "\uD83C\uDFA8",
title = "Design website",
content = "Design the website for the conference.",
),
"uC7hTQmG5F" to Note(
objectId = "uC7hTQmG5F",
icon = "\uD83D\uDC42",
title = "Attend meeting",
content = "Attend meeting with the team to discuss the conference.",
),
))
companion object {
@Volatile
private var instance: AppViewModel? = null
fun getInstance(): AppViewModel {
return instance ?: synchronized(this) {
instance ?: AppViewModel().also { instance = it }
}
}
}
}
- 데이터 변경 시 UI 업데이트를 트리거하기 위해 Compose의
MutableState를
사용했습니다. MutableState에는
데이터로 채운objectId와
Notes의
맵이
있습니다.앱뷰모델의
인스턴스가 단 하나만 존재하도록 하기 위해 싱글톤 패턴을 사용했습니다.
이제 앱뷰모델.getInstance()
를 통해 활동의 앱뷰모델
인스턴스에 액세스할 수 있습니다.
주요 활동
계속해서 MainActivity.kt 콘텐츠를 다음 코드로 대체하여 메모를 표시합니다:
// app/src/main/java.<your_package_name>/MainActivity.kt
package <your.package.name>
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
class MainActivity : ComponentActivity() {
private val viewModel = AppViewModel.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val context = this as Context
setContent {
MainActivityContent(
viewModel = viewModel,
onNoteListItemClick = {
// TODO: Open note edit form
},
onNoteAddClick = {
// TODO: Open note create form
},
)
}
}
}
@Composable
fun MainActivityContent(
viewModel: AppViewModel,
onNoteListItemClick: (note: Note) -> Unit,
onNoteAddClick: () -> Unit,
) {
val notes = viewModel.notes.value
MaterialTheme {
Scaffold(
topBar = { TopAppBar(title = { Text("My Notes") }) },
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = { onNoteAddClick() },
icon = { Icon(Icons.Filled.Add, contentDescription = "Add") },
text = { Text("Add") }
)
},
) { contentPadding ->
Box(modifier = Modifier.padding(contentPadding)) {
NoteList(notes, onNoteListItemClick = { onNoteListItemClick(it) })
}
}
}
}
@Composable
fun NoteListItem(note: Note, onNoteListItemClick: (note: Note) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onNoteListItemClick(note) })
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(text = note.icon, fontSize = 32.sp, modifier = Modifier.size(48.dp))
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(text = note.title, fontSize = 18.sp)
Spacer(modifier = Modifier.height(4.dp))
Text(
text = note.content,
fontSize = 14.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Composable
fun NoteList(notes: Map<String, Note>, onNoteListItemClick: (note: Note) -> Unit) {
LazyColumn {
items(notes.entries.toList()) { (_, note) ->
NoteListItem(note = note, onNoteListItemClick = onNoteListItemClick)
}
}
}
- 이제
앱뷰모델에서
노트를
가져오고 있습니다. - 컴포저블에서 클릭 이벤트를 정의하는 대신 인자로 전달했습니다.
- UI를 디자인할 때 Compose를 사용했습니다. 더 매끄럽게 만들기 위해
MaterialTheme을
스캐폴드
레이아웃과 통합했습니다.
지금 앱을 다시 빌드하면 화면에 ‘하드코딩된’ 노트 목록이 보일 것입니다.
노트 양식 활동
이 섹션에서는 사용자가 노트를 추가하고 편집할 수 있는 새로운 활동을 추가하겠습니다.
먼저 다음 내용으로 FormActivity라는
이름의 새 클래스를 만듭니다:
// app/src/main/java.<your_package_name>/FormActivity.kt
package <your.package.name>
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material3.Button
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
class FormActivity : ComponentActivity() {
private val viewModel = AppViewModel.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val objectId = intent.getStringExtra("objectId")
val note = if (objectId !== null) viewModel.notes.value[objectId] else null
setContent {
FormActivityContent(
note,
onNoteAddClick = { icon: String, title: String, content: String ->
if (note !== null) return@FormActivityContent
val newNote = Note(
icon = icon,
title = title,
content = content,
)
newNote.objectId = Note.generateObjectId()
viewModel.notes.value += (newNote.objectId!! to newNote)
finish()
},
onNoteSaveClick = { icon: String, title: String, content: String ->
if (note === null) return@FormActivityContent
val updatedNote = note.copy()
updatedNote.icon = icon
updatedNote.title = title
updatedNote.content = content
viewModel.notes.value += (updatedNote.objectId!! to updatedNote)
finish()
},
onNoteDeleteClick = {
if (note === null) return@FormActivityContent
viewModel.notes.value = viewModel.notes.value.filter {
it.value.objectId != note.objectId
}
finish()
},
)
}
}
}
@Composable
fun FormActivityContent(
note: Note?,
onNoteAddClick: (icon: String, title: String, content: String) -> Unit,
onNoteSaveClick: (icon: String, title: String, content: String) -> Unit,
onNoteDeleteClick: () -> Unit,
) {
MaterialTheme {
Scaffold(
topBar = {
TopAppBar(title = { Text(note?.let { "Edit Note" } ?: ("Add Note")) })
},
floatingActionButton = {
if (note !== null) {
ExtendedFloatingActionButton(
onClick = { onNoteDeleteClick() },
icon = { Icon(Icons.Filled.Delete, "Delete") },
text = { Text("Delete") },
)
}
},
) { contentPadding ->
Box(modifier = Modifier.padding(contentPadding)) {
NoteForm(note = note, onNoteSave = { icon, title, content ->
if (note === null) {
onNoteAddClick(icon, title, content)
} else {
onNoteSaveClick(icon, title, content)
}
})
}
}
}
}
@Composable
fun NoteForm(
note: Note?,
onNoteSave: (icon: String, title: String, content: String) -> Unit
) {
var icon by remember { mutableStateOf(TextFieldValue(note?.icon ?: "")) }
var title by remember { mutableStateOf(TextFieldValue(note?.title ?: "")) }
var content by remember { mutableStateOf(TextFieldValue(note?.content ?: "")) }
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
TextField(
label = { Text(text = "Icon") },
value = icon,
onValueChange = { icon = it },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
label = { Text(text = "Title") },
value = title,
onValueChange = { title = it },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
TextField(
label = { Text(text = "Content") },
value = content,
onValueChange = { content = it },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { onNoteSave(icon.text, title.text, content.text) },
Modifier.fillMaxWidth()
) {
Text(text = "Save")
}
}
}
- 이 양식은 노트 추가와 편집에 모두 사용됩니다. 동작을 결정하기 위해
objectId == null을
비교합니다.null이면
추가 작업이고, 그렇지 않으면 편집 작업입니다. objectId !== null이면
상태에서노트를
가져와 양식을 채웁니다.뮤터블스테이트가
제대로 작동하려면 그 내용을 불변으로 취급해야 했습니다. 그래서 때때로 내용을 수정하는 대신 복사하기도 했습니다.- 양식 상태 작성은
mutableStateOf()
를 통해 처리했습니다.
다음으로, 애플리케이션
하단의 AndroidManifest.xml에 FormActivity를
등록합니다:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<!-- ... -->
<activity android:name=".FormActivity"/>
</application>
</manifest>
마지막으로, MainActivity.kt에서 onNoteListItemClick
및 onNoteAddClick에
대한 새 인텐트를
만듭니다:
// app/src/main/java.<your_package_name>/MainActivity.kt
class MainActivity : ComponentActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
setContent {
MainActivityContent(
viewModel = viewModel,
onNoteListItemClick = {
// triggers the edit action, since `objectId` is provided
val intent = Intent(context, FormActivity::class.java)
intent.putExtra("objectId", it.objectId)
context.startActivity(intent)
},
onNoteAddClick = {
// triggers the add action, since no `objectId` is present
val intent = Intent(context, FormActivity::class.java)
context.startActivity(intent)
},
)
}
}
}
파일 상단에 있는 Intent를
가져오는 것을 잊지 마세요:
import android.content.Intent
애플리케이션을 다시 빌드하면 이제 제대로 작동하는 애플리케이션이 생겼음을 알 수 있습니다. 이 앱에서 노트를 추가하고, 편집하고, 삭제할 수 있습니다.
유일한 문제는 앱을 다시 시작하면 상태가 “하드코딩된” 노트로 초기화된다는 점입니다.
다음 섹션에서는 앱을 Back4app 백엔드에 연결하겠습니다. 이렇게 하면 상태를 유지하고 여러 기기 간에 동기화할 수 있습니다.
Parse SDK 설치
아시다시피 Back4app은 Parse 플랫폼을 기반으로 합니다. 일반적으로 Back4app 데이터베이스 또는 Back4app과 상호 작용하려면 Parse SDK를 설치해야 합니다.
settings.gradle에 JitPack 리포지토리를 추가하여 시작하세요:
// settings.gradle
pluginManagement {
repositories {
// ...
maven { url "https://jitpack.io" }
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
// ...
maven { url "https://jitpack.io" }
}
}
다음으로 앱 레벨의 build.gradle에 안드로이드 파싱 SDK를 추가합니다:
// app/build.gradle
dependencies {
// ...
implementation "com.github.parse-community.Parse-SDK-Android:parse:4.2.0"
}
Gradle 설정을 동기화하고 오류가 없는지 확인합니다.
구문 분석 SDK 구성
Back4app 백엔드에 연결하려면 Parse SDK에 애플리케이션 ID와 클라이언트 키를 제공해야 합니다. 자격 증명을 얻으려면 Back4app 앱으로 이동하여 사이드바에서 ‘앱 설정 > 보안 및 키’를 선택합니다.
그런 다음 다음과 같이 strings.xml에 추가합니다:
<!-- app/src/main/res/values/strings.xml -->
<resources>
<string name="app_name">back4app-android-app</string>
<string name="back4app_server_url">https://parseapi.back4app.com/</string>
<string name="back4app_app_id">your_parse_app_id</string>
<string name="back4app_client_key">your_parse_client_key</string>
</resources>
your_parse_app_id와
your_parse_client_key를
자격 증명으로 바꾸어야 합니다.
그런 다음, 인터넷 액세스를 활성화하고 자격 증명 메타데이터를 첨부하도록 AndroidManifest.xml을 수정합니다:
<!-- app/src/main/AndroidManifest.xml -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- these two permissions enable internet access -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<application>
<!-- ... -->
<!-- newly added metadata -->
<meta-data
android:name="com.parse.SERVER_URL"
android:value="@string/back4app_server_url" />
<meta-data
android:name="com.parse.APPLICATION_ID"
android:value="@string/back4app_app_id" />
<meta-data
android:name="com.parse.CLIENT_KEY"
android:value="@string/back4app_client_key" />
</application>
</manifest>
마지막으로 해야 할 일은 Parse를 초기화하는 것입니다. 모든 활동 전에 Parse가 초기화되도록 하기 위해 Application
클래스를 상속하는 App이라는
새 클래스를 생성합니다.
App.kt를 생성하고 다음과 같이 Parse를 초기화합니다:
// app/src/main/java/<your_package_name>/App.kt
package <your.package.name>
import android.app.Application
import com.parse.Parse
class App : Application() {
override fun onCreate() {
super.onCreate()
Parse.initialize(
Parse.Configuration.Builder(this)
.applicationId(getString(R.string.back4app_app_id))
.clientKey(getString(R.string.back4app_client_key))
.server(getString(R.string.back4app_server_url))
.build()
)
}
}
그런 다음 AndroidManifest.xml에 앱
클래스를 등록합니다:
<!-- app/src/main/AndroidManifest.xml -->
<application
android:name=".App"
...
>
<!-- ... -->
</application>
앱을 다시 한 번 재빌드하고 로그캣 창에서 오류가 있는지 확인합니다. 오류가 없으면 백4앱 연결에 성공한 것입니다.
Back4app에서 노트 로드
앱이 완성되기 전에 마지막으로 해야 할 일은 Back4app 데이터베이스에서 메모를 로드하고 실제로 Parse를 사용하는 방법을 살펴보는 것입니다.
먼저 앱뷰모델로
이동합니다. 맵에서 데이터를 제거하고 초기화
블록을 추가합니다:
// app/src/main/java/<your_package_name>/AppViewModel.kt
package <your.package.name>
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
class AppViewModel : ViewModel() {
val notes: MutableState<Map<String, Note>> = mutableStateOf(mapOf())
init {
val query = com.parse.ParseQuery.getQuery<com.parse.ParseObject>("Note")
query.orderByDescending("createdAt")
query.findInBackground { notes, e ->
if (e == null) {
for (parseNote in notes) {
val note = Note(
objectId = parseNote.objectId,
icon = parseNote.getString("icon")!!,
title = parseNote.getString("title")!!,
content = parseNote.getString("content")!!,
)
this.notes.value += (note.objectId!! to note)
}
} else {
println("Error: ${e.message}")
}
}
}
companion object {
// ...
}
}
이 코드는 Parse SDK를 사용하여 데이터베이스에서 노트를 가져와서 노트
데이터 클래스로 변환한 다음 맵에 저장합니다.
다음으로 다음 세 가지 방법을 노트에
추가합니다:
// app/src/main/java/<your_package_name>/Note.kt
package <your.package.name>
import com.parse.ParseObject
import com.parse.ParseQuery
data class Note(
var objectId: String? = null,
var icon: String,
var title: String,
var content: String,
) {
fun addToParse(callback: (objectId: String) -> Unit) {
if (objectId !== null) throw Exception("Note is already saved to Parse!")
val parseNote = ParseObject("Note")
parseNote.put("icon", icon)
parseNote.put("title", title)
parseNote.put("content", content)
parseNote.saveInBackground {
if (it !== null) throw Exception("Error: ${it.message}")
objectId = parseNote.objectId
callback(parseNote.objectId)
}
}
fun updateToParse(callback: (objectId: String) -> Unit) {
if (objectId === null) throw Exception("Note hasn't been saved to Parse yet!")
val query = ParseQuery.getQuery<ParseObject>("Note")
val parseNote = query.get(objectId)
parseNote.put("icon", icon)
parseNote.put("title", title)
parseNote.put("content", content)
parseNote.saveInBackground {
if (it !== null) throw Exception("Error: ${it.message}")
callback(parseNote.objectId)
}
}
fun deleteFromParse(callback: () -> Unit) {
if (objectId === null) throw Exception("Note hasn't been saved to Parse yet!")
val query = ParseQuery.getQuery<ParseObject>("Note")
val parseNote = query.get(objectId)
parseNote.deleteInBackground {
if (it !== null) throw Exception("Error: ${it.message}")
callback()
}
}
}
addToParse()는
세부 정보와 함께 새 노트를 Parse 서버에 추가합니다. 성공하면 노트의 고유 식별자를 저장하고 완료되면 알림을 보냅니다.updateToParse()는
Parse 서버에서 기존 노트의 정보를 업데이트합니다. 성공하면 변경 후 완료 신호를 보냅니다.deleteFromParse()는
Parse 서버에서 저장된 노트를 제거합니다. 성공하면 삭제가 완료된 것으로 확인합니다.
마지막으로, 새로 정의된 메소드를 호출하도록 FormActivity의
클릭 메소드를 수정합니다:
// app/src/main/java/<your_package_name>/NoteFormActivity.kt
class FormActivity : ComponentActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// ...
setContent {
FormActivityContent(
note,
onNoteAddClick = { icon: String, title: String, content: String ->
if (note !== null) return@FormActivityContent
val newNote = Note(
icon = icon,
title = title,
content = content,
)
newNote.addToParse {
viewModel.notes.value += (it to newNote)
finish()
}
},
onNoteSaveClick = { icon: String, title: String, content: String ->
if (note === null) return@FormActivityContent
val updatedNote = note.copy()
updatedNote.icon = icon
updatedNote.title = title
updatedNote.content = content
updatedNote.updateToParse {
viewModel.notes.value += (it to updatedNote)
finish()
}
},
onNoteDeleteClick = {
if (note === null) return@FormActivityContent
viewModel.notes.value = viewModel.notes.value.filter {
it.value.objectId != note.objectId
}
note.deleteFromParse {
finish()
}
},
)
}
}
}
그거예요!
이제 애플리케이션이 완전히 작동하고 Back4app 백엔드에 동기화되었습니다. 데이터를 변경하고 변경 사항이 데이터베이스 보기에 반영되는지 확인하여 앱을 사용해 보세요.
결론
이 문서에서는 모바일 백엔드를 성공적으로 빌드하고 Android 앱에서 연결했습니다. 백엔드에는 Back4app의 BaaS 솔루션을 사용했고, 프론트엔드에는 Jetpack Compose와 함께 Kotlin을 활용했습니다.
이제 모바일 백엔드의 작동 방식을 어느 정도 이해하고 직접 구축할 수 있을 것입니다. back4app-android-app 리포지토리에서 최종 소스 코드를 확인하세요.