1. 서론
애자일 방법론 관련해서 유명한 그림이 있다.
이번 프로젝트에 애자일을 적용해보자! 이런 건 아니지만 기능하는 최소 단위를 만들어서 서버가 잘 작동하는 걸 확인하고 싶었다. 그래서 클릭을 할 때마다 서버에 요청을 보내서 DB 저장된 값을 1 더하고, 이걸 응답받아 처리하는 앱을 만들 것이다.
2. 안드로이드 스튜디오로 앱 만들기
개발 툴은 갤럭시 유저이므로 안드로이드 스튜디오를 사용하기로 했다.
1) 안드로이드 스튜디오 다운로드
https://developer.android.com/studio?hl=ko
Android 스튜디오 및 앱 도구 다운로드 - Android 개발자 | Android Studio | Android Developers
Android Studio provides app builders with an integrated development environment (IDE) optimized for Android apps. Download Android Studio today.
developer.android.com
위 링크에서 실행파일을 다운받고 설치한다.
2) 프로젝트 생성
처음 실행하면 위와 같은 창이 나타난다. New Project를 클릭하고 Empty Activity를 선택한다.
3) http 통신을 위한 라이브러리 의존성 추가 (build.gradle.kts :app)
4) activity_main.xml 작성
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp">
<Button
android:id="@+id/incrementButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment Value" />
<TextView
android:id="@+id/resultTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Current Value: 0"
android:textSize="24sp" />
</LinearLayout>
5) 코틀린 코드 작성
// IncrementApi.kt
package com.example.test
import retrofit2.Call
import retrofit2.http.POST
interface IncrementApi {
@POST("api/increment")
fun incrementValue(): Call<IncrementResponse>
}
data class IncrementResponse(
val newValue: Int
)
// MainActivity.kt
package com.example.test
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.example.test.ui.theme.TestTheme
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class MainActivity : ComponentActivity() {
private lateinit var incrementButton: Button
private lateinit var resultTextView: TextView
private val retrofit = Retrofit.Builder()
.baseUrl("http://10.0.2.2:5000/")
.addConverterFactory(GsonConverterFactory.create())
.build()
private val incrementApi = retrofit.create(IncrementApi::class.java)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
incrementButton = findViewById(R.id.incrementButton)
resultTextView = findViewById(R.id.resultTextView)
// 버튼 클릭 시 서버에 요청 보내기
incrementButton.setOnClickListener {
incrementValueFromServer()
}
}
private fun incrementValueFromServer() {
incrementApi.incrementValue().enqueue(object : Callback<IncrementResponse> {
override fun onResponse(call: Call<IncrementResponse>, response: Response<IncrementResponse>) {
if (response.isSuccessful) {
val newValue = response.body()?.newValue ?: 0
resultTextView.text = "Current Value: $newValue"
} else {
resultTextView.text = "Error: ${response.code()}"
}
}
override fun onFailure(call: Call<IncrementResponse>, t: Throwable) {
resultTextView.text = "Failed to connect to server"
}
})
}
}
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
TestTheme {
Greeting("Android")
}
}
3. 테스트용 서버
간단하게 python으로 서버를 열어보자.
# server.py
from flask import Flask, jsonify
app = Flask(__name__)
# DB에서 관리하는 값 (간단한 예시)
db_value = 0
@app.route('/api/increment', methods=['POST'])
def increment_value():
global db_value
db_value += 1
return jsonify({'newValue': db_value})
if __name__ == '__main__':
app.run(debug=True)
가상환경을 만들기 위해 Pycharm 하단 Terminal에 아래 명령어를 입력한다.
python -m venv venv
venv\Scripts\activate
이후에 가상환경 내부로 들어가면 Flask를 설치하고 서버를 실행한다.
pip install flask
set FLASK_APP=server.py
flask run --host=0.0.0.0 --port=5000
4. 애뮬레이터 테스트 (실패)
안드로이드 스튜디오 애뮬레이터를 통해 테스트할 수 있다. 하지만 한 번에 잘 될 리가 없지!
서버 연결이 안 된다고 한다. 우선 로그 출력을 해보자.
override fun onFailure(call: Call<IncrementResponse>, t: Throwable) {
resultTextView.text = "Failed to connect to server"
Log.e("API_CALL", "Failed to connect to server: ${t.message}", t)
}
onFailure 메서드에 로그 출력 코드를 작성하고 Logcat에서 확인하니 http 통신이 불가능하다고 한다.
(CLEARTEXT communication to 10.0.2.2 not permitted by network security policy)
5. 문제 해결
안드로이드 9 이상에서는 http 통신을 기본적으로 차단하고 있다. 따라서 허용하도록 설정해줘야 한다.
우선 network_security_config.xml을 작성한다.
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>
그리고 AndroidManifest.xml application에 아래 한 줄을 추가한다.
android:networkSecurityConfig="@xml/network_security_config"
새로운 에러가 나를 맞이한다.
(Failed to connect to server: socket failed: EPERM (Operation not permitted))
인터넷 권한이 없어서 발생하는 문제로 AndroidManifest.xml에 아래 한 줄을 추가한다.
<uses-permission android:name="android.permission.INTERNET" />
6. 애뮬레이터 테스트 (성공)
클릭할 때마다 숫자가 올라가는 걸 확인할 수 있다.
7. 실제 기기 테스트
내친 김에 휴대폰에 실제로 설치해서 테스트해보자.
안드로이드 스튜디오 Build > Build Bundle(s) / APK(s) > Build APK(s) 를 선택하면 build\outputs\apk\debug 경로에 apk 파일이 생성된다. 이걸 휴대폰으로 옮긴 후 설치한다.
같은 wifi에 연결된 기기이므로 MainActivity.kt에서 10.0.2.2 대신 로컬 네트워크 IP 주소를 입력한다. http 통신을 위해 수정한 AndroidManifest.xml 파일에도 수정해주면 된다.
테스트 결과 아주 잘 작동한다.
8. 결론
안드로이드 스튜디오로 간단한 앱을 만들었고, python으로 서버를 열어 테스트까지 완료하였다. 나는 백엔드 개발자라 앱을 정교하게 만들 생각은 없고, 미니 PC가 배송되면 서버 환경 구축을 시작해야겠다.
'홈서버 만들기' 카테고리의 다른 글
[홈서버 만들기 - 5] VPN 서버 구축 (0) | 2024.09.28 |
---|---|
[홈서버 만들기 - 4] Linux 설치 후 원격 접속하기 (1) | 2024.09.22 |
GMKtec N100 미니 PC 언박싱 (0) | 2024.09.21 |
[홈서버 만들기 - 2] 아키텍처 설계 (0) | 2024.09.15 |
[홈서버 만들기 - 1] 장비 구매 (2) | 2024.09.13 |