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/app/build.gradle.kts b/app/build.gradle.kts
index 0c6dbbc..f6849db 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,13 @@ 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 +47,7 @@ android {
}
buildFeatures {
compose = true
+ buildConfig = true
}
}
@@ -68,10 +78,16 @@ dependencies {
// Coroutines
implementation(libs.kotlinx.coroutines.android)
+ implementation(libs.adapter.guava)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.test.manifest)
+
+ 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..7bccc1e
--- /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 android.R.attr.value
+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(value = "/api/posts")
+ suspend fun getPosts(): BaseResponse>
+
+ @POST(value= "/api/posts")
+ suspend fun createPost(
+ @Query(value = "author") author: String = "경민",
+ @Body request: PostCreateRequest
+ ): BaseResponse
+
+ @GET(value= "/api/posts/{id}")
+ suspend fun getPostDetail(
+ @Path("id") id: Long
+ ): BaseResponse
+
+ @PUT(value = "/api/posts/{id}")
+ suspend fun editPost(
+ @Path(value= "id") id: Long,
+ @Body request: PostCreateRequest
+ ): BaseResponse
+
+ @DELETE(value = "/api/posts/{id}")
+ suspend fun deletePost(
+ @Path(value = "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..dfb3309
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/api/RetrofitClient.kt
@@ -0,0 +1,31 @@
+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..a826a7d
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/request/PostCreateRequest.kt
@@ -0,0 +1,9 @@
+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?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthResponse.kt b/app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthResponse.kt
new file mode 100644
index 0000000..20cb35f
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/response/AuthResponse.kt
@@ -0,0 +1,10 @@
+package com.example.kuit6_android_api.data.model.response
+
+import android.R.attr.value
+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..5630cd1
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/response/BaseResponse.kt
@@ -0,0 +1,11 @@
+package com.example.kuit6_android_api.data.model.response
+
+import android.R.attr.value
+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..7d7ff8a
--- /dev/null
+++ b/app/src/main/java/com/example/kuit6_android_api/data/model/response/PostResponse.kt
@@ -0,0 +1,13 @@
+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..b5344a8 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 = "이것은 샘플 게시글 내용입니다. 미리보기에서는 두 줄까지만 표시됩니다.",
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..e887899 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
@@ -45,6 +45,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.kuit6_android_api.ui.post.viewmodel.PostViewModel
+
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PostCreateScreen(
@@ -187,8 +188,8 @@ fun PostCreateScreen(
Button(
onClick = {
- val finalAuthor = author.ifBlank { "anonymous" }
- viewModel.createPost(finalAuthor, title, content, null) {
+ val finalAuthor = author
+ viewModel.createPost(finalAuthor, title, content,null) {
onPostCreated()
}
},
diff --git a/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostListScreen.kt b/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostListScreen.kt
index 8522d79..e48ac6e 100644
--- a/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostListScreen.kt
+++ b/app/src/main/java/com/example/kuit6_android_api/ui/post/screen/PostListScreen.kt
@@ -23,6 +23,8 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.example.kuit6_android_api.ui.post.component.PostItem
import com.example.kuit6_android_api.ui.post.viewmodel.PostViewModel
+import com.example.kuit6_android_api.data.model.response.AuthorResponse
+import com.example.kuit6_android_api.data.model.response.PostResponse
@OptIn(ExperimentalMaterial3Api::class)
@Composable
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..8601526 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
@@ -1,96 +1,73 @@
package com.example.kuit6_android_api.ui.post.viewmodel
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateListOf
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 kotlinx.coroutines.delay
import kotlinx.coroutines.launch
-import java.time.LocalDateTime
+import com.example.kuit6_android_api.data.api.RetrofitClient
+import com.example.kuit6_android_api.data.model.response.PostResponse
+import com.example.kuit6_android_api.data.model.request.PostCreateRequest
-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
+class PostViewModel : ViewModel() {
- var posts by mutableStateOf>(emptyList())
+ var posts by mutableStateOf>(emptyList())
private set
- var postDetail by mutableStateOf(null)
+ var postDetail by mutableStateOf(null)
private set
+ private val apiService = RetrofitClient.apiService
var uploadedImageUrl by mutableStateOf(null)
private set
fun getPosts() {
viewModelScope.launch {
- delay(500) // 네트워크 시뮬레이션
- posts = dummyPosts.toList()
+ runCatching {
+ apiService.getPosts()
+ }.onSuccess { response ->
+ response.data?.let {
+ if (response.success) {
+ posts = response.data
+ }
+ }
+ }
}
}
fun getPostDetail(postId: Long) {
viewModelScope.launch {
- delay(300)
- postDetail = dummyPosts.find { it.id == postId }
+ runCatching {
+ apiService.getPostDetail(postId)
+ }.onSuccess { response ->
+ response.data?.let{
+ if(response.success){
+ postDetail = response.data
+ }
+ }
+ }
}
}
fun createPost(
- author: String = "anonymous",
+ author: String,
title: String,
content: String,
imageUrl: String? = null,
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()
+ }
+ }
}
}
@@ -102,30 +79,26 @@ 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()
- )
- dummyPosts[index] = updatedPost
- postDetail = updatedPost
- posts = dummyPosts.toList()
- onSuccess()
+ runCatching {
+ apiService.editPost(postId, PostCreateRequest(title, content, imageUrl))
+ }.onSuccess{ response->
+ if(response.success){
+ postDetail = response.data
+ onSuccess()
+ }
}
}
}
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()
+ }
+ }
}
}
@@ -133,7 +106,4 @@ class PostViewModel : ViewModel() {
uploadedImageUrl = null
}
- private fun getCurrentDateTime(): String {
- return LocalDateTime.now().toString()
- }
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9576441..10c96d0 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -29,6 +29,7 @@ coil = "2.7.0"
# Coroutines
coroutines = "1.9.0"
+adapterGuava = "2.11.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -67,6 +68,7 @@ coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coi
# Coroutines
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
+adapter-guava = { group = "com.squareup.retrofit2", name = "adapter-guava", version.ref = "adapterGuava" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }