diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index a2cf405..639c779 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -9,8 +9,8 @@
diff --git a/CLAUDE.md b/CLAUDE.md
deleted file mode 100644
index b18ed6b..0000000
--- a/CLAUDE.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# CLAUDE.md
-
-This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
-
-## Project Overview
-
-KUIT6_Android_API is an Android application project using Kotlin and Gradle with the Kotlin DSL.
-
-**Package name**: `com.example.kuit6_android_api`
-
-**Build configuration**:
-- Kotlin 2.0.21
-- Android Gradle Plugin 8.13.0
-- Compile SDK: 36
-- Min SDK: 28
-- Target SDK: 36
-- Java 11 (sourceCompatibility, targetCompatibility, and jvmTarget)
-
-## Common Commands
-
-### Build and Run
-```bash
-# Clean build
-./gradlew clean
-
-# Build the project
-./gradlew build
-
-# Build debug APK
-./gradlew assembleDebug
-
-# Build release APK
-./gradlew assembleRelease
-
-# Install debug APK to connected device/emulator
-./gradlew installDebug
-```
-
-### Testing
-```bash
-# Run unit tests (in test/)
-./gradlew test
-
-# Run unit tests for debug variant
-./gradlew testDebugUnitTest
-
-# Run instrumented tests on connected device/emulator (in androidTest/)
-./gradlew connectedAndroidTest
-
-# Run specific test class
-./gradlew test --tests com.example.kuit6_android_api.ExampleUnitTest
-
-# Run specific test method
-./gradlew test --tests com.example.kuit6_android_api.ExampleUnitTest.addition_isCorrect
-```
-
-### Code Quality
-```bash
-# Lint the project
-./gradlew lint
-
-# Generate lint report
-./gradlew lintDebug
-```
-
-### Project Info
-```bash
-# List all tasks
-./gradlew tasks
-
-# Show project dependencies
-./gradlew dependencies
-
-# Show app module dependencies
-./gradlew :app:dependencies
-```
-
-## Project Structure
-
-```
-app/
-├── src/
-│ ├── main/
-│ │ ├── java/com/example/kuit6_android_api/ # Main source code
-│ │ ├── res/ # Android resources
-│ │ └── AndroidManifest.xml
-│ ├── test/ # Unit tests (JVM)
-│ └── androidTest/ # Instrumented tests (Android device)
-├── build.gradle.kts # App-level build configuration
-└── proguard-rules.pro # ProGuard rules for release builds
-
-build.gradle.kts # Project-level build configuration
-settings.gradle.kts # Project settings
-gradle/libs.versions.toml # Version catalog for dependencies
-```
-
-## Dependencies
-
-Dependencies are managed via the Gradle version catalog in `gradle/libs.versions.toml`.
-
-Current dependencies:
-- AndroidX Core KTX
-- AndroidX AppCompat
-- Material Components
-- JUnit (unit testing)
-- AndroidX Test JUnit (instrumented testing)
-- Espresso (UI testing)
-
-To add new dependencies, update `gradle/libs.versions.toml` and reference them in `app/build.gradle.kts`.
-
-## Architecture Notes
-
-This is a fresh Android project with minimal code. The main source directory (`app/src/main/java/com/example/kuit6_android_api/`) is currently empty. When implementing features:
-
-- Place activities, fragments, and UI components in the main package or appropriate subpackages
-- Follow standard Android architecture patterns (MVVM, MVI, etc.) as needed
-- Use the existing test infrastructure for unit tests (test/) and instrumented tests (androidTest/)
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 0c6dbbc..2a38695 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,3 +1,5 @@
+import java.util.Properties
+
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@@ -18,6 +20,14 @@ android {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ val properties = Properties()
+ val localPropertiesFile = rootProject.file("local.properties")
+ if (localPropertiesFile.exists()) {
+ properties.load(localPropertiesFile.inputStream())
+ }
+ val baseUrl = properties.getProperty("BASE_URL")
+ buildConfigField("String", "BASE_URL", "\"$baseUrl\"")
+
}
buildTypes {
@@ -38,6 +48,7 @@ android {
}
buildFeatures {
compose = true
+ buildConfig = true
}
}
@@ -74,4 +85,12 @@ dependencies {
androidTestImplementation(libs.androidx.espresso.core)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
+
+
+ // Retrofit & OkHttp
+ implementation(libs.retrofit)
+ implementation(libs.retrofit.converter.gson)
+ implementation(libs.okhttp)
+ implementation(libs.okhttp.logging.interceptor)
+
}
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/api/ApiService.kt b/app/src/main/java/com/example/kuit6_android_api/data/api/ApiService.kt
new file mode 100644
index 0000000..b6a6a1d
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/api/ApiService.kt
@@ -0,0 +1,40 @@
+package com.example.kuit6_android_api.data.api
+
+import com.example.kuit6_android_api.data.model.request.PostCreateRequest
+import com.example.kuit6_android_api.data.model.response.BaseResponse
+import com.example.kuit6_android_api.data.model.response.PostResponse
+import retrofit2.http.Body
+import retrofit2.http.DELETE
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.PUT
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface ApiService {
+
+ @GET("api/posts")
+ suspend fun getPosts(): BaseResponse>
+
+ @GET("api/posts/{id}")
+ suspend fun getPostDetail(
+ @Path("id") id: Long
+ ): BaseResponse
+
+ @POST("api/posts")
+ suspend fun createPost(
+ @Query("author") author: String = "규빈",
+ @Body request: PostCreateRequest
+ ): BaseResponse
+
+ @PUT("api/posts/{id}")
+ suspend fun updatePost(
+ @Path("id") id: Long,
+ @Body request: PostCreateRequest
+ ): BaseResponse
+
+ @DELETE("api/posts/{id}")
+ suspend fun deletePost(
+ @Path("id") id: Long
+ ): BaseResponse
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt b/app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt
new file mode 100644
index 0000000..897b8b7
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt
@@ -0,0 +1,30 @@
+package com.example.kuit6_android_api.data.api
+
+import com.example.kuit6_android_api.BuildConfig
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import java.util.concurrent.TimeUnit
+
+object RetrofitClient {
+
+ private val loggingInterceptor = HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+
+ private val okHttpClient = OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .writeTimeout(30, TimeUnit.SECONDS)
+ .build()
+
+ private val retrofit: Retrofit = Retrofit.Builder()
+ .baseUrl(BuildConfig.BASE_URL)
+ .client(okHttpClient)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+
+ val apiService: ApiService = retrofit.create(ApiService::class.java)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt b/app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt
deleted file mode 100644
index e4c1d08..0000000
--- a/app/src/main/java/com/example/kuit6_android_api/data/model/Post.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.example.kuit6_android_api.data.model
-
-data class Author(
- val id: Long,
- val username: String,
- val profileImageUrl: String?
-)
-
-data class Post(
- val id: Long,
- val title: String,
- val content: String,
- val imageUrl: String?,
- val author: Author,
- val createdAt: String,
- val updatedAt: String
-)
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt b/app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt
new file mode 100644
index 0000000..7b10d54
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt
@@ -0,0 +1,11 @@
+package com.example.kuit6_android_api.data.model.request
+
+import kotlinx.serialization.SerialName
+
+data class PostCreateRequest(
+
+ @SerialName(value = "title") val title: String,
+ @SerialName(value = "content") val content: String,
+ @SerialName(value = "imageUrl") val imageUrl: String?
+
+ )
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthorResponse.kt b/app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthorResponse.kt
new file mode 100644
index 0000000..99345ea
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthorResponse.kt
@@ -0,0 +1,10 @@
+package com.example.kuit6_android_api.data.model.response
+
+import kotlinx.serialization.SerialName
+
+data class AuthorResponse(
+
+ @SerialName( value = "id") val id: Long,
+ @SerialName(value = "username") val username: String,
+ @SerialName(value = "profileImageUrl") val profileImageUrl: String?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt b/app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt
new file mode 100644
index 0000000..9da563d
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt
@@ -0,0 +1,10 @@
+package com.example.kuit6_android_api.data.model.response
+
+import kotlinx.serialization.SerialName
+
+data class BaseResponse(
+ @SerialName(value = "success") val success: Boolean,
+ @SerialName(value= "message") val message: String?,
+ @SerialName(value = "data") val data: T?,
+ @SerialName(value = "timestamp") val timestamp: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/model/response/PostResponse.kt b/app/src/main/java/com/example/kuit6_android_api/data/model/response/PostResponse.kt
new file mode 100644
index 0000000..741c4c0
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/response/PostResponse.kt
@@ -0,0 +1,14 @@
+package com.example.kuit6_android_api.data.model.response
+
+import kotlinx.serialization.SerialName
+
+data class PostResponse(
+ @SerialName(value = "id") val id: Long,
+ @SerialName(value = "title")val title: String,
+ @SerialName(value = "content")val content: String,
+ @SerialName(value = "imageUrl")val imageUrl: String?,
+ @SerialName(value = "author")val author: AuthorResponse,
+ @SerialName(value = "createdAt")val createdAt: String,
+ @SerialName(value = "updatedAt")val updatedAt: String
+
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt b/app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt
index df2e5fe..a14bf26 100644
--- a/app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt
+++ b/app/src/main/java/com/example/kuit6_android_api/ui/post/component/PostItem.kt
@@ -29,13 +29,13 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
-import com.example.kuit6_android_api.data.model.Author
-import com.example.kuit6_android_api.data.model.Post
+import com.example.kuit6_android_api.data.model.response.AuthorResponse
+import com.example.kuit6_android_api.data.model.response.PostResponse
import com.example.kuit6_android_api.util.formatDateTime
@Composable
fun PostItem(
- post: Post,
+ post: PostResponse,
onClick: () -> Unit
) {
Card(
@@ -153,12 +153,12 @@ fun PostItem(
fun PostItemPreview() {
MaterialTheme {
PostItem(
- post = Post(
+ post = PostResponse(
id = 1,
title = "샘플 게시글 제목",
- content = "이것은 샘플 게시글 내용입니다. 미리보기에서는 두 줄까지만 표시됩니다.",
+ content = "이것은 샘플 게시글 앙. 미리보기에서는 두 줄까지만 표시됩니다.",
imageUrl = null,
- author = Author(1, "testuser", null),
+ author = AuthorResponse(1, "testuser", null),
createdAt = "2025-10-03T12:00:00",
updatedAt = "2025-10-03T12:00:00"
),
diff --git a/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt b/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt
index 57ea2ab..5f0ce47 100644
--- a/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt
+++ b/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostCreateScreen.kt
@@ -187,7 +187,7 @@ fun PostCreateScreen(
Button(
onClick = {
- val finalAuthor = author.ifBlank { "anonymous" }
+ val finalAuthor = author
viewModel.createPost(finalAuthor, title, content, null) {
onPostCreated()
}
diff --git a/app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt b/app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt
index 444229c..826ff58 100644
--- a/app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt
+++ b/app/src/main/java/com/example/kuit6_android_api/ui/post/viewmodel/PostViewModel.kt
@@ -6,67 +6,54 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import com.example.kuit6_android_api.data.model.Author
-import com.example.kuit6_android_api.data.model.Post
+import com.example.kuit6_android_api.data.api.RetrofitClient
+import com.example.kuit6_android_api.data.model.request.PostCreateRequest
+import com.example.kuit6_android_api.data.model.response.PostResponse
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.time.LocalDateTime
class PostViewModel : ViewModel() {
- // 더미 데이터
- private val dummyPosts = mutableStateListOf(
- Post(
- id = 1,
- title = "Jetpack Compose 시작하기",
- content = "Jetpack Compose는 Android의 최신 UI 툴킷입니다. 선언형 UI로 더 쉽고 빠르게 UI를 만들 수 있습니다.",
- imageUrl = null,
- author = Author(1, "개발자A", null),
- createdAt = "2025-10-05T10:00:00",
- updatedAt = "2025-10-05T10:00:00"
- ),
- Post(
- id = 2,
- title = "Kotlin Coroutines 완벽 가이드",
- content = "비동기 프로그래밍을 쉽게! Coroutines를 사용하면 복잡한 비동기 코드를 간단하게 작성할 수 있습니다.",
- imageUrl = null,
- author = Author(2, "개발자B", null),
- createdAt = "2025-10-05T11:30:00",
- updatedAt = "2025-10-05T11:30:00"
- ),
- Post(
- id = 3,
- title = "Android MVVM 아키텍처",
- content = "MVVM 패턴으로 코드를 구조화하면 테스트와 유지보수가 쉬워집니다. ViewModel과 LiveData/StateFlow를 활용해봅시다.",
- imageUrl = null,
- author = Author(1, "개발자A", null),
- createdAt = "2025-10-05T14:20:00",
- updatedAt = "2025-10-05T14:20:00"
- )
- )
-
- private var nextId = 4L
-
- var posts by mutableStateOf>(emptyList())
+ var posts by mutableStateOf>(emptyList())
private set
- var postDetail by mutableStateOf(null)
+ var postDetail by mutableStateOf(null)
private set
var uploadedImageUrl by mutableStateOf(null)
private set
+ private val apiService = RetrofitClient.apiService
+
+
+
fun getPosts() {
- viewModelScope.launch {
- delay(500) // 네트워크 시뮬레이션
- posts = dummyPosts.toList()
+
+ viewModelScope.launch{
+ runCatching {
+ apiService.getPosts()
+ }.onSuccess { response ->
+ if (response.success && response.data != null) {
+ posts = response.data
+ }
+ }
+
+
}
+
}
fun getPostDetail(postId: Long) {
viewModelScope.launch {
- delay(300)
- postDetail = dummyPosts.find { it.id == postId }
+ runCatching { apiService.getPostDetail(postId) }
+ .onSuccess { response ->
+ if (response.success && response.data != null) {
+ postDetail = response.data
+ }
+ }
+ .onFailure {
+ }
}
}
@@ -78,20 +65,17 @@ class PostViewModel : ViewModel() {
onSuccess: () -> Unit = {}
) {
viewModelScope.launch {
- delay(500)
- val newPost = Post(
- id = nextId++,
- title = title,
- content = content,
- imageUrl = imageUrl,
- author = Author(nextId, author, null),
- createdAt = getCurrentDateTime(),
- updatedAt = getCurrentDateTime()
- )
- dummyPosts.add(0, newPost)
- posts = dummyPosts.toList()
- onSuccess()
+ runCatching {
+ val request = PostCreateRequest(title, content, imageUrl)
+ apiService.createPost(author, request)
+ }.onSuccess { response ->
+ if (response.success) {
+ clearUploadedImageUrl()
+ onSuccess()
+ }
+ }
}
+
}
fun updatePost(
@@ -102,30 +86,30 @@ class PostViewModel : ViewModel() {
onSuccess: () -> Unit = {}
) {
viewModelScope.launch {
- delay(500)
- val index = dummyPosts.indexOfFirst { it.id == postId }
- if (index != -1) {
- val oldPost = dummyPosts[index]
- val updatedPost = oldPost.copy(
- title = title,
- content = content,
- imageUrl = imageUrl,
- updatedAt = getCurrentDateTime()
+ runCatching {
+ apiService.updatePost(
+ postId,
+ PostCreateRequest(title = title, content = content, imageUrl = imageUrl)
)
- dummyPosts[index] = updatedPost
- postDetail = updatedPost
- posts = dummyPosts.toList()
- onSuccess()
+ }.onSuccess { response ->
+ if (response.success) {
+ onSuccess()
+ }
+ }.onFailure {
}
}
}
fun deletePost(postId: Long, onSuccess: () -> Unit = {}) {
viewModelScope.launch {
- delay(300)
- dummyPosts.removeIf { it.id == postId }
- posts = dummyPosts.toList()
- onSuccess()
+ runCatching { apiService.deletePost(postId) }
+ .onSuccess { response ->
+ if (response.success) {
+ onSuccess()
+ }
+ }
+ .onFailure {
+ }
}
}