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

Doodung step2 #7

Open
wants to merge 3 commits into
base: doodung
Choose a base branch
from
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
22 changes: 22 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}

android {
Expand All @@ -16,6 +17,11 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildFeatures {
dataBinding true
viewBinding true
}

buildTypes {
release {
minifyEnabled false
Expand All @@ -40,4 +46,20 @@ dependencies {
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'androidx.activity:activity-ktx:1.1.0'
implementation 'androidx.fragment:fragment-ktx:1.2.5'

// Lifecycle
implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

// Retrofit
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.retrofit2:adapter-rxjava2:2.5.0"

//Glide
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
}
10 changes: 7 additions & 3 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"
package="place.pic.android.plus">

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

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand All @@ -10,14 +12,16 @@
android:supportsRtl="true"
android:theme="@style/Theme.AssignmentMVVM">
<activity
android:name=".MainActivity"
android:exported="true">
android:name=".detail.DetailUserActivity"
android:exported="false" />
<activity
android:name=".search.SearchUserActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
49 changes: 49 additions & 0 deletions app/src/main/java/place/pic/android/plus/BindingAdapter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package place.pic.android.plus

import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide

object BindingAdapter {
@BindingAdapter("setImageUrl")
@JvmStatic
fun loadImage(imageView: ImageView, url: String) {
Glide.with(imageView.context)
.load(url)
.circleCrop()
.into(imageView)
}

@BindingAdapter("setButtonChange")
@JvmStatic
fun setButtonChange(button: ImageView, compass: Boolean) {
when (compass) {
true -> button.setBackgroundResource(R.drawable.ic_baseline_clear_24)
else -> button.setBackgroundResource(R.drawable.ic_baseline_search_24)
}
}

/*
@BindingAdapter("TextChangeWatcher")
@JvmStatic
fun onEditTextWatcher(): TextWatcher {
return object : TextWatcher {
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable) {
}
}
}

@BindingAdapter("bindRecyclerView")
@JvmStatic
fun bindRecyclerView(recyclerView: RecyclerView, searchUserData: MutableList<SearchUserData>?)
{
if (recyclerView.adapter != null) {
with(recyclerView.adapter as SearchUserAdapter) {
submitList(searchUserData)
}
}
}
*/
}
11 changes: 0 additions & 11 deletions app/src/main/java/place/pic/android/plus/MainActivity.kt

This file was deleted.

43 changes: 43 additions & 0 deletions app/src/main/java/place/pic/android/plus/SingleLiveEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package place.pic.android.plus

import android.util.Log
import androidx.annotation.MainThread
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

open class SingleLiveEvent<T> : MutableLiveData<T>() {

private val mPending = AtomicBoolean(false)

override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
}

super.observe(
owner,
Observer { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
)
}

@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}

@MainThread
fun call() {
value = null
}

companion object {
private val TAG = "SingleLiveEvent"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package place.pic.android.plus.data.remote

import place.pic.android.plus.data.remote.response.ResponseUserSearch
import retrofit2.http.GET
import retrofit2.http.Query

interface GithubService {
@GET("/search/users")
suspend fun userList(
@Query("q") param: String?
): ResponseUserSearch
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package place.pic.android.plus.data.remote

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitBuilder {
private const val baseUrl = "https://api.github.com"

private var retrofit = Retrofit.Builder().baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()

var service: GithubService = retrofit.create(GithubService::class.java)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package place.pic.android.plus.data.remote.response

import java.io.Serializable

data class ResponseUserSearch(
val total_count: Int,
val incomplete_results: Boolean,
val items: List<SearchUserData>
) : Serializable

data class SearchUserData(
val login: String,
val avatar_url: String,
val html_url: String
) : Serializable
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package place.pic.android.plus.detail

import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import place.pic.android.plus.data.remote.response.SearchUserData
import place.pic.android.plus.databinding.ActivityDetailUserBinding

class DetailUserActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailUserBinding
Copy link
Contributor

Choose a reason for hiding this comment

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

코틀린은 불변성을 권장하는 언어라는 특징이 있어 "권장" 이기 때문에 대부분의 경우에서는 불변성을 자동으로 제공해주지만 프로그래머에게 그 역할이 위임 되어있는 경우도 존재한다 생각해

여기선 큰 문제점은 보이지 않지만 앞으로 코틀린을 계속 다룬다면 한번은 생각해보고 넘어가면 좋을 것 같아

애초에 여기서 binding 변수는 반드시 프로퍼티로 존재할 이유가 없다고 생각

Copy link
Author

Choose a reason for hiding this comment

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

옳습니다


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDetailUserBinding.inflate(layoutInflater)
setContentView(binding.root)
setBindingUserData()
}

private fun setBindingUserData() {
Copy link
Contributor

Choose a reason for hiding this comment

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

일단 이 함수가 하는 일은 무엇일까? 보다 간단한 이름이 있지는 않을지 고민해보면 좋을 것 같다는 생각이 들어

보다 간단한 이름을 생각해보면 이 함수가 하는 일이 과연 엑티비티가 해야할 역할에 포함되는지 생각해볼 수 있을 것 같아

Copy link
Author

Choose a reason for hiding this comment

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

넵! 수정하게씁니다!

val user = intent.getSerializableExtra("user") as SearchUserData
Copy link
Contributor

Choose a reason for hiding this comment

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

intent.getSerializableExtra("user") 이 친구의 결과물은 뭘까?

지금 요구 사항에서는 필요 없는 부분 이지만

만약 DetailUserActivity 가 user 라는 정보를 받지 못하고 실행되었다면 어떻게 되어야 할지 처리해주는 것도 고려해봐야 하지 않을까?

Copy link
Author

Choose a reason for hiding this comment

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

넵! 반영하겠습니다.


with(binding) {
tvUser.text = user.login

Glide.with(this@DetailUserActivity)
.load(user.avatar_url)
.circleCrop()
.into(imgUser)

btnUser.setOnClickListener {
val webIntent =
Intent(Intent.ACTION_VIEW, Uri.parse(user.html_url))
startActivity(webIntent)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package place.pic.android.plus.search

import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import place.pic.android.plus.R
import place.pic.android.plus.data.remote.response.SearchUserData
import place.pic.android.plus.databinding.ActivitySearchUserBinding
import place.pic.android.plus.detail.DetailUserActivity
import place.pic.android.plus.search.adapter.SearchUserAdapter
import place.pic.android.plus.search.viewmodel.SearchUserViewModel

class SearchUserActivity : AppCompatActivity() {
private lateinit var binding: ActivitySearchUserBinding
private val searchUserViewModel: SearchUserViewModel by viewModels()
private lateinit var searchUserAdapter: SearchUserAdapter
private val userList = mutableListOf<SearchUserData>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_search_user)
searchUserAdapter = SearchUserAdapter()
binding.searchUserActivity = searchUserViewModel
binding.lifecycleOwner = this
binding.rvUserSearch.adapter = searchUserAdapter

searchUser()
changeButton()
deleteText()
gotoDetail()
}

private fun searchUser() {
searchUserViewModel.recyclerListData.observe(this) {
searchUserAdapter.submitList(it)
}

binding.etUserSearch.setOnEditorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
lifecycleScope.launch {
searchUserViewModel.requestUserData(binding.etUserSearch.text.toString())
}
return true
}
return false
}
})
}

// livedata 쓰기
private fun changeButton() {
binding.etUserSearch.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
binding.btnUserSearch.visibility = View.VISIBLE
binding.btnUserSearchDelete.visibility = View.INVISIBLE
}

override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}

override fun afterTextChanged(p0: Editable?) {
binding.btnUserSearch.visibility = View.INVISIBLE
binding.btnUserSearchDelete.visibility = View.VISIBLE
}
})
}

// Btn 바인딩으로빼기
private fun deleteText() {
binding.btnUserSearchDelete.setOnClickListener {
binding.etUserSearch.text.clear()
}
}

// 여기 바꿔야함요 ㅎㅎ
private fun gotoDetail() {
searchUserAdapter.itemClick = object : SearchUserAdapter.ItemClick {
override fun onClick(view: View, position: Int) {
val intent = Intent(this@SearchUserActivity, DetailUserActivity::class.java)

intent.putExtra("user", userList[position])
startActivity(intent)
}
}
}
}
Loading