Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MVVM - Repository, ViewModel구현 #3

Open
wants to merge 26 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
370532b
api통신을 위한 의존성 추가 및 buildConfig, viewBinding사용설정
Leesin0222 Dec 24, 2024
a92ef14
viewBinding적용으로 인한 view id수정
Leesin0222 Dec 24, 2024
faadea2
api통신을 사용하며 기존 인 메모리에서 사용되던 RecentReadCardListManager및 Card data class제거
Leesin0222 Dec 24, 2024
d82e06b
api통신 실패 시 보여줄 에러 메시지 정의
Leesin0222 Dec 24, 2024
17664cf
Manifest에 인터넷 권한 명시 및 cleartextTraffic사용하도록 설정
Leesin0222 Dec 24, 2024
aa342b1
CardResponse 구현
Leesin0222 Dec 24, 2024
26fabe3
cardApi 및 RetrofitBuilder구현
Leesin0222 Dec 24, 2024
702797e
토스트 확장 함수 구현
Leesin0222 Dec 24, 2024
24b2e1e
LifeCycle 코루틴 처리 확장함수 구현
Leesin0222 Dec 24, 2024
d907d09
MainActivity에 viewBinding적용
Leesin0222 Dec 24, 2024
0f56f12
AllCardListAdapter에 viewBinding적용 및 updateAllCard함수 구현
Leesin0222 Dec 24, 2024
bc88ce5
CardViewHolder에 viewBinding적용
Leesin0222 Dec 24, 2024
bb14c63
RecentReadCardListAdapter에 viewBinding적용 및 첫 번째 아이템 제거 함수 구현
Leesin0222 Dec 24, 2024
c0bc3a5
AllCardListFragment에 뷰바인딩 적용 및 모든 카드를 api 호출하여 받아오도록 구현
Leesin0222 Dec 24, 2024
180b637
RecentReadCardListFragment에 viewBinding적용 및 api호출 로직 구현
Leesin0222 Dec 24, 2024
86f6c2a
CardDetailActivity에 viewBinding적용 및 api호출 로직 구현
Leesin0222 Dec 24, 2024
d930bbe
Glide의존성 추가 및 imageUrl불러와 image띄워주는 로직 구현
Leesin0222 Dec 24, 2024
110c96d
getExtra type수정
Leesin0222 Dec 24, 2024
c4396a2
changeCardReadStatus api 함수에 Card return값 추가
Leesin0222 Jan 3, 2025
f3a8498
RepositoryBuilder와 CardRepository구현
Leesin0222 Jan 3, 2025
cd3f572
최근 읽은 카드 목록 화면의 ViewModel 및 ViewModelFactory추가
Leesin0222 Jan 3, 2025
f681277
카드 상세 화면의 ViewModel 및 ViewModelFactory추가
Leesin0222 Jan 3, 2025
fa72ae3
모든 카드 목록 화면의 ViewModel 및 ViewModelFactory추가
Leesin0222 Jan 3, 2025
1034815
api 요청 시 모든 카드 목록 ViewModel을 사용하도록 수정
Leesin0222 Jan 3, 2025
dc6b9c6
api 요청 시 최근 읽은 카드 목록 ViewModel을 사용하도록 수정
Leesin0222 Jan 3, 2025
df1115c
api 요청 시 카드상세 ViewModel을 사용하도록 수정
Leesin0222 Jan 3, 2025
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
11 changes: 11 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.kotlin.serialization)
}

android {
Expand Down Expand Up @@ -33,6 +34,10 @@ android {
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
buildConfig = true
viewBinding = true
}
}

dependencies {
Expand All @@ -43,6 +48,12 @@ dependencies {
implementation(libs.androidx.activity)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.lifecycle.runtime)
implementation(libs.retrofit)
implementation(libs.kotlinx.serialization.json)
implementation(libs.retrofit.kotlin.serialization)
implementation(libs.okhttp)
implementation(libs.okhttp.logging)
implementation(libs.glide)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
3 changes: 3 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 All @@ -11,6 +13,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidAssignment"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".MainActivity"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,30 @@ import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import com.google.android.material.tabs.TabLayout
import com.yongjincompany.android_assignment.databinding.ActivityCardDetailBinding
import com.yongjincompany.android_assignment.databinding.ActivityMainBinding
import com.yongjincompany.android_assignment.feature.home.AllCardListFragment
import com.yongjincompany.android_assignment.feature.home.RecentReadCardListFragment

class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}

val tabLayout: TabLayout = findViewById(R.id.tl_list_tab)

//init fragment
replaceFragment(RecentReadCardListFragment())

tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
binding.tlListTab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
when (tab.position) {
0 -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.yongjincompany.android_assignment.core.util

import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

fun Fragment.repeatOnLifecycleState(
state: Lifecycle.State = Lifecycle.State.STARTED,
block: suspend CoroutineScope.() -> Unit
) =
viewLifecycleOwner.launchRepeatOnLifecycleState(state, block)

fun AppCompatActivity.repeatOnLifecycleState(
state: Lifecycle.State = Lifecycle.State.STARTED,
block: suspend CoroutineScope.() -> Unit
): Job =
launchRepeatOnLifecycleState(state, block)

private fun LifecycleOwner.launchRepeatOnLifecycleState(
state: Lifecycle.State = Lifecycle.State.STARTED,
block: suspend CoroutineScope.() -> Unit
) =
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(state, block)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yongjincompany.android_assignment.core.util

import android.content.Context
import android.widget.Toast

fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.yongjincompany.android_assignment.data

import com.yongjincompany.android_assignment.data.RetrofitBuilder.cardApi
import com.yongjincompany.android_assignment.data.repository.CardRepository

object RepositoryBuilder {
val cardRepository = CardRepository(cardApi)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.yongjincompany.android_assignment.data

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.yongjincompany.android_assignment.BuildConfig
import com.yongjincompany.android_assignment.data.api.CardApi
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit

object RetrofitBuilder {
private const val BASE_URL = "http://10.0.2.2:8080/api/"

private val json = Json {
ignoreUnknownKeys = true
coerceInputValues = true
}

private val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}

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

private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()

val cardApi: CardApi = retrofit.create(CardApi::class.java)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.yongjincompany.android_assignment.data.api

import com.yongjincompany.android_assignment.data.model.response.Card
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.Path
import retrofit2.http.Query

interface CardApi {
@GET("card")
suspend fun fetchAllCardList(): List<Card>

@GET("card/read")
suspend fun fetchRecentReadCardList(): List<Card>

@PATCH("card/read/{id}")
suspend fun changeCardReadStatus(
@Path("id") id: Long,
@Query("isRead") isRead: Boolean
): Card
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.yongjincompany.android_assignment.data.model.response

import kotlinx.serialization.Serializable

@Serializable
data class Card(
val description: String,
val grade: CardGradeType,
val id: Long,
val imageUrl: String,
val isRead: Boolean,
val name: String,
)

@Serializable
enum class CardGradeType {
S,
A,
B,
C,
D,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.yongjincompany.android_assignment.data.repository

import com.yongjincompany.android_assignment.data.api.CardApi
import com.yongjincompany.android_assignment.data.model.response.Card

class CardRepository(private val cardApi: CardApi) {
suspend fun fetchAllCardList(): Result<List<Card>> =
kotlin.runCatching { cardApi.fetchAllCardList() }

suspend fun fetchRecentReadCardList(): Result<List<Card>> =
kotlin.runCatching { cardApi.fetchRecentReadCardList() }

suspend fun changeCardReadStatus(
id: Long,
isRead: Boolean
): Result<Card> =
kotlin.runCatching { cardApi.changeCardReadStatus(id, isRead) }
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
package com.yongjincompany.android_assignment.feature.home

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.yongjincompany.android_assignment.data.Card
import com.yongjincompany.android_assignment.data.Grade
import com.yongjincompany.android_assignment.R
import androidx.lifecycle.ViewModelProvider
import com.yongjincompany.android_assignment.core.util.SpacingItemDecoration
import com.yongjincompany.android_assignment.core.util.repeatOnLifecycleState
import com.yongjincompany.android_assignment.data.RepositoryBuilder
import com.yongjincompany.android_assignment.databinding.FragmentAllCardListBinding
import com.yongjincompany.android_assignment.feature.home.adapter.AllCardListAdapter
import com.yongjincompany.android_assignment.feature.home.viewmodel.AllCardListViewModel
import com.yongjincompany.android_assignment.feature.home.viewmodel.AllCardListViewModelFactory

class AllCardListFragment : Fragment(R.layout.fragment_all_card_list) {
class AllCardListFragment : Fragment() {
private var _binding: FragmentAllCardListBinding? = null
private val binding get() = _binding!!
private lateinit var allCardListAdapter: AllCardListAdapter
private lateinit var vm: AllCardListViewModel

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAllCardListBinding.inflate(inflater, container, false)
val view = binding.root
return view
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val allCardListRecyclerView: RecyclerView = view.findViewById(R.id.rv_all_card_list)
val allCardList = listOf(
Card(id = 0, grade = Grade.D, name = "사죄 용진", img = R.drawable.sorry_yongjin_card_img, description = "왜 이 사람은 책상 위에 올라가 사죄하고 있을까요? 전 알아요 그는 세미콜론이라는 동아리를 나가서 새 길을 개척하겠다는 포부를 가지고 있다는 것을"),
Card(id = 1, grade = Grade.C, name = "민달팽이 정민", img = R.drawable.snail_jeongmin_card_img, description = "문정민, 그는 프랭키 그 자체입니다. 그의 얼굴을 보고 절대 속으면 안됩니다. 끔찍하게도 그의 얼굴과 달리 그의 몸은 민달팽이와 같은 구조로 되어있습니다."),
Card(id = 2, grade = Grade.B, name = "이별 영준", img = R.drawable.brakeup_youngjun_card_img, description = "만약 저런 표정으로 누워있는 그를 발견한다면 그를 절대로 건드리지 않는 것이 좋을겁니다. 그는 안좋은 일을 겪었거든요…"),
Card(id = 3, grade = Grade.A, name = "마스터이 경수", img = R.drawable.masteryi_gyeongsu_card_img, description = "그는 잠자리일까요? 사람일까요? 그의 정체를 아는 사람은 없을겁니다… 아마도요.."),
Card(id = 4, grade = Grade.A, name = "앙큼한 진성", img = R.drawable.cute_jinsung_card_img, description = "오우 그를 보셨나요? 그는 참 상큼발랄한 친구입니다. 그의 이름이요? 황진성입니다. 그는 웹개발자이죠.")
)

allCardListRecyclerView.adapter = AllCardListAdapter(allCardList)
allCardListRecyclerView.addItemDecoration(SpacingItemDecoration(30))
init()
observe()
}

private fun init() {
val viewModelFactory = AllCardListViewModelFactory(RepositoryBuilder.cardRepository)
vm = ViewModelProvider(this, viewModelFactory)[AllCardListViewModel::class.java]

allCardListAdapter = AllCardListAdapter()
binding.rvAllCardList.adapter = allCardListAdapter
binding.rvAllCardList.addItemDecoration(SpacingItemDecoration(30))

vm.fetchAllCardList()
}

private fun observe() {
repeatOnLifecycleState {
vm.allCardList.collect {
allCardListAdapter.updateAllCard(vm.allCardList.value)
}
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

Loading
Loading