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

[ Team2 (Jay&Stitch) ] recyclerview 클릭 이벤트 + MVVM 패턴 적용 #18

Merged
merged 3 commits into from
Mar 25, 2022
Merged
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 app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ android {

dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'androidx.fragment:fragment-ktx:1.4.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.5.0'
Expand Down
8 changes: 4 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.Kotlinphotos">
<activity
android:name=".photos.DoodleActivity"
android:name=".ui.doodle.DoodleActivity"
android:exported="false" />
<activity
android:name=".photos.PhotoActivity"
android:name=".ui.photo.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -25,10 +25,10 @@
</intent-filter>
</activity>
<activity
android:name=".previous.SecondActivity"
android:name=".permission.SecondActivity"
android:exported="true" />
<activity
android:name=".previous.MainActivity"
android:name=".permission.MainActivity"
android:exported="false" />
</application>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.example.kotlinphotos.photos
package com.example.kotlinphotos

import android.content.Context
import java.lang.Exception
import java.lang.NullPointerException

object AssetLoader {
class AssetLoader(private val context: Context) {

private fun loadAsset(context: Context, fileName: String): String {
private fun loadAsset(fileName: String): String {
return context.assets.open(fileName).use { inputStream ->
val size = inputStream.available()
val byteArray = ByteArray(size)
Expand All @@ -15,9 +13,9 @@ object AssetLoader {
}
}

fun jsonToString(context: Context, fileName: String): String? {
fun jsonToString(fileName: String): String? {
return kotlin.runCatching {
loadAsset(context, fileName)
loadAsset(fileName)
}.getOrNull()
}
}
17 changes: 17 additions & 0 deletions app/src/main/java/com/example/kotlinphotos/model/Photo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.example.kotlinphotos.model

import android.graphics.Bitmap
import com.example.kotlinphotos.model.Type.*

data class Photo(
val tittle: String,
val bitmap: Bitmap?,
val date: String,
var isChecked: Boolean = false,
var mode: Type = READ
)

enum class Type(val mode: Int) {
READ(1),
EDIT(2)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.kotlinphotos.previous
package com.example.kotlinphotos.permission

import android.Manifest
import android.content.Intent
Expand Down

This file was deleted.

31 changes: 0 additions & 31 deletions app/src/main/java/com/example/kotlinphotos/photos/DoodleAdapter.kt

This file was deleted.

5 changes: 0 additions & 5 deletions app/src/main/java/com/example/kotlinphotos/photos/Photo.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.example.kotlinphotos.repository

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import com.example.kotlinphotos.AssetLoader
import com.example.kotlinphotos.model.Photo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.json.JSONArray
import java.net.URL

class DoodleAssetDataSource(
private val assetLoader: AssetLoader
) : DoodleDataSource {

override suspend fun getDoodleData(): List<Photo> {
val photos = mutableListOf<Photo>()
val str = assetLoader.jsonToString(DOODLE_FILE_NAME)
val jsonArray = JSONArray(str)

val size = jsonArray.length()
repeat(size) {
val json = jsonArray.getJSONObject(it)
val title = json.getString("title")
val image = json.getString("image")
val date = json.getString("date")
val bitmap = loadImage(image)
photos.add(Photo(title, bitmap, date))
}
return photos
}

private suspend fun loadImage(imageUrl: String): Bitmap? {
return withContext(Dispatchers.IO) {
kotlin.runCatching {
val url = URL(imageUrl)
val stream = url.openStream()
BitmapFactory.decodeStream(stream)
}.getOrNull()
}
}

companion object {
const val DOODLE_FILE_NAME = "doodle.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.kotlinphotos.repository

import com.example.kotlinphotos.model.Photo

interface DoodleDataSource {

suspend fun getDoodleData(): List<Photo>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.kotlinphotos.repository

import com.example.kotlinphotos.model.Photo

class DoodleRepository(
private val doodleAssetDataSource: DoodleAssetDataSource
) {
suspend fun loadDoodlePhotos(): List<Photo> {
return doodleAssetDataSource.getDoodleData()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.example.kotlinphotos.photos
package com.example.kotlinphotos.ui.common

import androidx.recyclerview.widget.DiffUtil
import com.example.kotlinphotos.model.Photo


class PhotosDiffCallback : DiffUtil.ItemCallback<Photo>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.example.kotlinphotos.ui.doodle

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.ImageButton
import androidx.activity.viewModels
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinphotos.R
import com.example.kotlinphotos.ui.common.PhotosDiffCallback

class DoodleActivity : AppCompatActivity() {
private lateinit var backButton: ImageButton
private lateinit var recyclerView: RecyclerView
private lateinit var saveImageButton: ImageButton
private val viewModel: DoodleViewModel by viewModels { DoodleViewModelFactory(this) }

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_doodle)

backButton = findViewById(R.id.image_button_back)
recyclerView = findViewById(R.id.recyclerview_doodle)
saveImageButton = findViewById(R.id.image_button_save)

backButton.setOnClickListener { finish() }

val doodleAdapter = DoodleAdapter(PhotosDiffCallback(), object : OnSaveListener {
override fun showSaveButton() {
saveImageButton.visibility = View.VISIBLE
}
})
recyclerView.adapter = doodleAdapter
recyclerView.layoutManager = GridLayoutManager(this, 3)
viewModel.photos.observe(this) { doodleAdapter.submitList(it) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.example.kotlinphotos.ui.doodle

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.kotlinphotos.R
import com.example.kotlinphotos.model.Photo
import com.example.kotlinphotos.model.Type.*

class DoodleAdapter(
diffCallback: DiffUtil.ItemCallback<Photo>,
private val saveListener: OnSaveListener
) : ListAdapter<Photo, DoodleAdapter.DoodleViewHolder>(diffCallback) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DoodleViewHolder {
return DoodleViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.item, parent, false)
)
}

override fun onBindViewHolder(holder: DoodleViewHolder, position: Int) {
val photo = getItem(position)
holder.bind(getItem(position))

holder.itemView.setOnLongClickListener {
Copy link
Contributor

Choose a reason for hiding this comment

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

@bn-tw2020 @shim506 이 코드는 holder의 itemView를 꺼내서 Listener를 할당하고 있네요~
bind 메서드 내부로 이동시키는 편이 낫지 않을까요? 어떻게 생각하세요? 바로 아래 코드도 마찬가지입니다.

Copy link
Collaborator

@shim506 shim506 Mar 25, 2022

Choose a reason for hiding this comment

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

다음 PR 에 수정내용 반영해서 보내겠습니다.

for (current in currentList) {
current.mode = EDIT
}
false
}
holder.itemView.setOnClickListener {
if (photo.mode == EDIT) {
currentList[position].isChecked = true
Copy link
Contributor

@ivybae ivybae Mar 24, 2022

Choose a reason for hiding this comment

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

@bn-tw2020 @shim506 Edit모드를 구현한 방식이 수정이 필요해 보입니다.

  1. 작성하신 코드의 흐름을 정리해보면 Long Click -> List 모든 원소의 mode 값 변경, false를 반환해서 이벤트를 소비하지 않고 -> ClickListener에서 EDIT 모드일 때 isChecked를 바꾸도록 처리한 부분은 이해하기가 너무 어려워 보여요. 단순한 기능인데도요.
  • Long Click이 발생했을 때 EDIT으로 변경하고 isChecked 까지 변경하는 것은 어려울까요?

Copy link
Contributor

Choose a reason for hiding this comment

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

  1. ListAdapter를 상속받았는데도 notifyDataSetChanged()를 호출하게 되는 점은 아쉽습니다.
    이부분은 1번 내용을 수정하고 개선하실 수 있다고 생각합니다.
    그리고 saveListener를 전달하신 것처럼 clickListener 혹은 longClickListener 구현을 생성자로 전달하는 방식도 생각해볼 수 있다고 생각합니다. 다른 구현방식도 있을테니 한번 고민해보시면 좋겠습니다.

Copy link
Collaborator

Choose a reason for hiding this comment

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

1-1 . 위와 같이 구현한 이유는

  • 아이템 롱클릭을 통해 EDIT 모드로 전체 아이템이 선택할 수 있는 UI로 변할 수 있게 햇고
  • 체크박스 영역에서의 일반 클릭(숏클릭)뿐 아니라 이미지 영역 위에서의 일반 클릭 통해 해당 아이템을 선택할 수 있게했습니다.
    롱클릭안에 모든 구현을 넣을 경우 일반 클릭(숏클릭)으로는 해당 이미지를 선택할 수 없게되기에 다음과 같이 구현했습니다.
    @step4me

Copy link
Contributor

Choose a reason for hiding this comment

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

@shim506 왜 이렇게 구현하신지는 알죠~ 제가 수정 의견을 드렸던 이유는 이벤트는 LongClick으로 발생했는데 데이터 변경 처리를 바로하지 않고 다른 이벤트 처리기로 이동시키는 부분이 아쉬웠기 때문입니다~

saveListener.showSaveButton()
notifyDataSetChanged()
}
}
}

inner class DoodleViewHolder(view: View) : RecyclerView.ViewHolder(view) {

private val photoImageView = view.findViewById<ImageView>(R.id.view_photo)
private val photoCheckBox = view.findViewById<CheckBox>(R.id.checkbox_select)

fun bind(photo: Photo) {
when (photo.mode) {
EDIT -> photoCheckBox.visibility = View.VISIBLE
READ -> photoCheckBox.visibility = View.INVISIBLE
}
photoCheckBox.setOnCheckedChangeListener(null)
photoCheckBox.isChecked = photo.isChecked
photoCheckBox.setOnCheckedChangeListener { _, value -> photo.isChecked = value }
photoImageView.setImageBitmap(photo.bitmap)
}
}
}
Loading