Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/encodings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id ("kotlin-parcelize")
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.0"
}

android {
Expand All @@ -18,6 +22,9 @@ android {
vectorDrawables {
useSupportLibrary = true
}

buildConfigField("String", "BASE_URL", gradleLocalProperties(rootDir).getProperty("BASE_URL"))

}

buildTypes {
Expand All @@ -29,6 +36,7 @@ android {
)
}
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
Expand All @@ -38,6 +46,7 @@ android {
}
buildFeatures {
compose = true
buildConfig = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
Expand Down Expand Up @@ -77,4 +86,20 @@ dependencies {
implementation("org.orbit-mvi:orbit-viewmodel:4.4.0")
implementation("org.orbit-mvi:orbit-compose:4.4.0")
testImplementation("org.orbit-mvi:orbit-test:4.4.0")

// openApi
implementation ("com.squareup.retrofit2:retrofit:2.9.0")
implementation ("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
implementation ("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")

implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0"))

implementation("com.squareup.okhttp3:okhttp")
implementation("com.squareup.okhttp3:logging-interceptor")

implementation ("com.squareup.okhttp3:okhttp:4.11.0")
implementation ("com.squareup.okhttp3:logging-interceptor:4.10.0")
implementation ("com.squareup.retrofit2:converter-gson:2.1.0")

implementation("io.coil-kt:coil-compose:2.6.0")
}
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand Down
40 changes: 40 additions & 0 deletions app/src/main/java/org/sopt/do_sopt_compose/data/ApiFactory.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.sopt.do_sopt_compose.data

import android.util.Log
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.sopt.do_sopt_compose.BuildConfig.BASE_URL
import org.sopt.do_sopt_compose.data.service.PeopleApiService
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object ApiFactory {

private fun getLogOkHttpClient(): Interceptor {
val loggingInterceptor = HttpLoggingInterceptor { message ->
Log.d("Retrofit2", "CONNECTION INFO -> $message")
}
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}

private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(getLogOkHttpClient())
.build()

fun retrofit(url: String): Retrofit =
Retrofit.Builder()
.baseUrl(url)
.client(okHttpClient)
//.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.addConverterFactory(GsonConverterFactory.create())
Comment on lines +30 to +31

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converter를 Gson으로 변경하신 이유가 있을까요!!?

.build()

inline fun <reified T, B> create(url: B): T =
retrofit(url.toString()).create(T::class.java)
}

object ServicePool {
val apiService = ApiFactory.create<PeopleApiService, String>(BASE_URL)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.sopt.do_sopt_compose.data.dto

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


@Serializable
data class ResponsePeopleDto(
@SerialName("page")
val page: Int,
@SerialName("per_page")
val perPage: Int,
@SerialName("total")
val total: Int,
@SerialName("total_pages")
val totalPages: Int,
@SerialName("data")
val data: List<User>,
@SerialName("support")
val support: Support,
)

@Serializable
data class User(
@SerialName("id")
val id: Int,
@SerialName("email")
val email: String,
@SerialName("first_name")
val firstName: String,
@SerialName("last_name")
val lastName: String,
@SerialName("avatar")
val avatar: String,
)

@Serializable
data class Support(
@SerialName("url")
val url: String,
@SerialName("text")
val text: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.do_sopt_compose.data.repository

import org.sopt.do_sopt_compose.data.ServicePool

class PeopleRepository {

private val apiService = ServicePool.apiService
suspend fun getUsers(page: Int) = apiService.getUsers(page, 10)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.sopt.do_sopt_compose.data.service

import org.sopt.do_sopt_compose.data.dto.ResponsePeopleDto
import retrofit2.http.GET
import retrofit2.http.Query

interface PeopleApiService {
@GET("users")
suspend fun getUsers(
@Query("page") page: Int,
@Query("per_page") perPage: Int,
): ResponsePeopleDto
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import org.sopt.do_sopt_compose.ui.pages.doandroid.DoAndroid
import org.sopt.do_sopt_compose.ui.pages.doandroid.DoAndroidScreen
import org.sopt.do_sopt_compose.ui.pages.doandroid.DoAndroidViewModel
import org.sopt.do_sopt_compose.ui.pages.home.HomePage
import org.sopt.do_sopt_compose.ui.pages.login.LoginPage
import org.sopt.do_sopt_compose.ui.pages.mypage.MainPage
Expand Down Expand Up @@ -50,7 +51,7 @@ private fun NavGraphBuilder.addMain(navController: NavController) {

private fun NavGraphBuilder.addDo(navController: NavController) {
composable(route = BottomNaviItems.Do.route) {
DoAndroid(
DoAndroidScreen(
navController = navController,
)
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package org.sopt.do_sopt_compose.ui.pages.doandroid

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import coil.size.Size
import org.sopt.do_sopt_compose.R
import org.sopt.do_sopt_compose.data.dto.User
import org.sopt.do_sopt_compose.navigation.BottomNavigation

@Composable
fun DoAndroidScreen(
navController: NavController,
) {
val viewModel: DoAndroidViewModel = viewModel()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적으로 뷰모델은 파라미터로 두는 것이 여러가지로 이점이 있다고 생각합니다이 :)

val usersState = viewModel.users.collectAsState()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lifecycle-runtime 의존성을 추가해서 collectAsStateWithLifecycle 를 사용하면 생명주기에 상태를 안전하게 관리할 수 있을 것 같습니다! 아니면 이미 orbit 라이브러리 쓰시니 orbit collectAsState api를 사용해도 좋을 것 같아요!

dependencies {
    implementation "androidx.lifecycle:lifecycle-runtime-compose:[version]"
}

https://developer.android.com/jetpack/androidx/releases/lifecycle


Scaffold(
bottomBar = { BottomNavigation(navController = navController) },
content = {
Box(
modifier = Modifier
.fillMaxSize()
.padding(it),
) {
UserList(users = usersState.value)
}
}
)
}

@Composable
fun UserList(users: List<User>) {
LazyColumn {
items(users) { user ->
UserItem(user)
}
}
}

@Composable
fun UserItem(user: User) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
) {
Row(
modifier = Modifier.padding(8.dp)
) {
// 프로필 이미지 표시
Image(
painter =
rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current).data(data = user.avatar)
.size(Size.ORIGINAL)
.apply(block = fun ImageRequest.Builder.() {
placeholder(R.drawable.profile_background)
}).build()
),
contentDescription = "Profile Picture",
modifier = Modifier
.size(64.dp)
.padding(end = 16.dp)
)
Column {
Text(
text = "ID: ${user.id}",
modifier = Modifier.padding(bottom = 4.dp)
)
Text(
text = "Name: ${user.firstName} ${user.lastName}",
modifier = Modifier.padding(bottom = 4.dp)
)
Text(
text = "Email: ${user.email}",
modifier = Modifier.padding(bottom = 4.dp)
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.sopt.do_sopt_compose.ui.pages.doandroid

sealed class DoAndroidSideEffect {
data class ToastMessage(val message: String) : DoAndroidSideEffect()
Copy link

@Jokwanhee Jokwanhee Apr 28, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 데이터 클래스가 프로퍼티로 message를 가지고 있어서 ToastMessage라는 클래스 네이밍은 함수형 프로그래밍에 있어서 조금 어색하다고 생각해요!!

예를 들어서,

  • 메시지가 하나가 아닌 여러 메시지를 전송한다면?
  • 프로퍼티 네이밍이 message 에서 messages 로 네이밍이 변경된다.
  • 클래스 네이밍도 ToastMessage 에서 ToastMessages 로 변경되어야 하는가?

그래서 다른 클래스 네이밍을 고민해보는 건 어떠신가요!? 👻👻👻

함수형 프로그래밍에 대한 글 Medium 읽어보시면 좋을 것 같아요!

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.do_sopt_compose.ui.pages.doandroid

import org.sopt.do_sopt_compose.ui.UiStatus

data class DoAndroidState(
val uiStatus: UiStatus = UiStatus.Loading,
)
Loading