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

HW-4 DataBinding 적용 #543

Merged
merged 13 commits into from
Mar 28, 2020
Merged
5 changes: 3 additions & 2 deletions 2003/mtjin/AndroidArchitectureStudy/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ apply plugin: 'com.android.application'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

apply plugin: 'kotlin-kapt'

android {
Expand All @@ -27,6 +25,9 @@ android {
targetCompatibility = "8"
sourceCompatibility = "8"
}
dataBinding {
enabled = true
}
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,51 +2,44 @@ package com.mtjin.androidarchitecturestudy.ui.login

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.data.login.source.LoginRepository
import com.mtjin.androidarchitecturestudy.data.login.source.LoginRepositoryImpl
import com.mtjin.androidarchitecturestudy.data.login.source.local.LoginLocalDataSource
import com.mtjin.androidarchitecturestudy.data.login.source.local.LoginLocalDataSourceImpl
import com.mtjin.androidarchitecturestudy.databinding.ActivityLoginBinding
import com.mtjin.androidarchitecturestudy.ui.search.MovieSearchActivity
import com.mtjin.androidarchitecturestudy.utils.PreferenceManager

class LoginActivity : AppCompatActivity(), LoginContract.View {
private lateinit var presenter: LoginContract.Presenter
private lateinit var etId: EditText
private lateinit var etPw: EditText
private lateinit var btnLogin: Button

private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
initView()
initListener()
initDataBinding()
inject()
}

private fun initDataBinding() {
binding =
DataBindingUtil.setContentView(this, R.layout.activity_login)
binding.login = this
}

private fun inject() {
val preferenceManager = PreferenceManager(this)
val loginLocalDataSource: LoginLocalDataSource = LoginLocalDataSourceImpl(preferenceManager)
val loginRepository: LoginRepository = LoginRepositoryImpl(loginLocalDataSource)
presenter = LoginPresenter(this, loginRepository)
}

private fun initView() {
etId = findViewById(R.id.et_id)
etPw = findViewById(R.id.et_pw)
btnLogin = findViewById(R.id.btn_login)
}

private fun initListener() {
btnLogin.setOnClickListener {
val id = etId.text.toString().trim()
val pw = etPw.text.toString().trim()
presenter.doLogin(id, pw)
}
fun onLoginClick() {
val id = binding.etId.text.toString().trim()
val pw = binding.etPw.text.toString().trim()
presenter.doLogin(id, pw)
}


Expand All @@ -56,11 +49,11 @@ class LoginActivity : AppCompatActivity(), LoginContract.View {
}

override fun showIdEmptyError() {
etId.error = getString(R.string.id_empty_error_msg)
binding.etId.error = getString(R.string.id_empty_error_msg)
}

override fun showPwEmptyError() {
etPw.error = getString(R.string.pw_empty_error_msg)
binding.etPw.error = getString(R.string.pw_empty_error_msg)
}

override fun goMovieSearch() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
package com.mtjin.androidarchitecturestudy.ui.search

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RatingBar
import android.widget.TextView
import androidx.core.text.HtmlCompat
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.data.search.Movie
import com.mtjin.androidarchitecturestudy.databinding.ItemMovieBinding

class MovieAdapter :
class MovieAdapter(private val itemClick: (Movie) -> Unit) :
RecyclerView.Adapter<MovieAdapter.ViewHolder>() {
private lateinit var clickCallBack: (Movie) -> Unit
lateinit var binding: ItemMovieBinding
private val items: ArrayList<Movie> = ArrayList()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view: View = LayoutInflater.from(parent.context)
.inflate(R.layout.item_movie, parent, false)
val viewHolder = ViewHolder(view)
view.setOnClickListener {
clickCallBack(items[viewHolder.adapterPosition])
binding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.item_movie,
parent,
false
)
val viewHolder = ViewHolder(binding)
binding.root.setOnClickListener {
itemClick(items[viewHolder.adapterPosition])
}

return viewHolder
Expand All @@ -36,38 +36,19 @@ class MovieAdapter :
}
}

class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

private val ivPoster = itemView.findViewById<ImageView>(R.id.iv_poster)
private val rvRating = itemView.findViewById<RatingBar>(R.id.rb_rating)
private val tvTitle = itemView.findViewById<TextView>(R.id.tv_title)
private val tvReleaseDate = itemView.findViewById<TextView>(R.id.tv_release_date)
private val tvActor = itemView.findViewById<TextView>(R.id.tv_actor)
private val tvDirector = itemView.findViewById<TextView>(R.id.tv_director)
class ViewHolder(private val binding: ItemMovieBinding) :
RecyclerView.ViewHolder(binding.root) {

fun bind(movie: Movie) {
with(movie) {
Glide.with(itemView).load(image)
.placeholder(R.drawable.ic_default)
.into(ivPoster!!)
rvRating.rating = (userRating.toFloatOrNull() ?: 0f) / 2
tvTitle.text = HtmlCompat.fromHtml(title, HtmlCompat.FROM_HTML_MODE_COMPACT)
tvReleaseDate.text = pubDate
tvActor.text = actor
tvDirector.text = director
}
binding.movie = movie
Hwangjunhong marked this conversation as resolved.
Show resolved Hide resolved
}
}

fun setItems(items: List<Movie>) {
fun addItems(items: List<Movie>) {
this.items.addAll(items)
notifyDataSetChanged()
}

fun setItemClickListener(clickCallBack: (Movie) -> Unit) {
this.clickCallBack = clickCallBack
}

fun clear() {
this.items.clear()
notifyDataSetChanged()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,21 @@ import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ProgressBar
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.mtjin.androidarchitecturestudy.R
import com.mtjin.androidarchitecturestudy.data.search.Movie
import com.mtjin.androidarchitecturestudy.databinding.ActivityMovieSearchBinding
import com.mtjin.androidarchitecturestudy.utils.MyApplication


class MovieSearchActivity : AppCompatActivity(), MovieSearchContract.View {

private lateinit var binding: ActivityMovieSearchBinding
private lateinit var presenter: MovieSearchContract.Presenter
private lateinit var etInput: EditText
private lateinit var btnSearch: Button
private lateinit var rvMovies: RecyclerView
private lateinit var pbLoading: ProgressBar
private lateinit var movieAdapter: MovieAdapter
private lateinit var myApplication: MyApplication
private lateinit var query: String
Expand All @@ -31,54 +27,49 @@ class MovieSearchActivity : AppCompatActivity(), MovieSearchContract.View {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_movie_search)

initView()
initDataBinding()
inject()
initAdapter()
initListener()
presenter = MovieSearchPresenter(this, myApplication.movieRepository)
}

private fun initView() {
private fun initDataBinding() {
binding = DataBindingUtil.setContentView(this, R.layout.activity_movie_search)
binding.search = this
}

private fun inject() {
myApplication = application as MyApplication
etInput = findViewById(R.id.et_input)
btnSearch = findViewById(R.id.btn_search)
rvMovies = findViewById(R.id.rv_movies)
pbLoading = findViewById(R.id.pb_loading)
presenter = MovieSearchPresenter(this, myApplication.movieRepository)
}

private fun initAdapter() {
movieAdapter = MovieAdapter()
movieAdapter = MovieAdapter { movie ->
Intent(Intent.ACTION_VIEW, Uri.parse(movie.link)).takeIf {
it.resolveActivity(packageManager) != null
}?.run(this::startActivity)
}
val linearLayoutManager = LinearLayoutManager(this)
rvMovies.layoutManager = linearLayoutManager
binding.rvMovies.layoutManager = linearLayoutManager
Copy link
Contributor

Choose a reason for hiding this comment

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

LayoutManager는 xml에서 설정 할 수 있어요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

5d6aff2
수정했습니다.

scrollListener = object : EndlessRecyclerViewScrollListener(linearLayoutManager) {
override fun onLoadMore(page: Int, totalItemsCount: Int, view: RecyclerView?) {
presenter.requestPagingMovie(query, totalItemsCount + 1)
}
}
rvMovies.addOnScrollListener(scrollListener)
rvMovies.adapter = movieAdapter
binding.rvMovies.addOnScrollListener(scrollListener)
binding.rvMovies.adapter = movieAdapter
}

private fun initListener() {
//어댑터 아이템 클릭리스너
movieAdapter.setItemClickListener { movie ->
Intent(Intent.ACTION_VIEW, Uri.parse(movie.link)).takeIf {
it.resolveActivity(packageManager) != null
}?.run(this::startActivity)
}
//검색버튼
btnSearch.setOnClickListener {
query = etInput.text.toString().trim()
presenter.requestMovie(query)
}
fun onSearchClick() {
query = binding.etInput.text.toString().trim()
presenter.requestMovie(query)
}

override fun showLoading() {
pbLoading.visibility = View.VISIBLE
binding.pbLoading.visibility = View.VISIBLE
}

override fun hideLoading() {
pbLoading.visibility = View.GONE
binding.pbLoading.visibility = View.GONE
}

override fun showToast(msg: String) {
Expand Down Expand Up @@ -111,12 +102,12 @@ class MovieSearchActivity : AppCompatActivity(), MovieSearchContract.View {

override fun searchMovieSuccess(movieList: List<Movie>) {
movieAdapter.clear()
movieAdapter.setItems(movieList)
movieAdapter.addItems(movieList)
Toast.makeText(this, getString(R.string.load_movie_success_msg), Toast.LENGTH_SHORT).show()
}

override fun pagingMovieSuccess(movieList: List<Movie>) {
movieAdapter.setItems(movieList)
movieAdapter.addItems(movieList)
Toast.makeText(this, getString(R.string.load_movie_success_msg), Toast.LENGTH_SHORT).show()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.mtjin.androidarchitecturestudy.utils

import android.os.Build
import android.text.Html
import android.widget.ImageView
import android.widget.RatingBar
import android.widget.TextView
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide
import com.mtjin.androidarchitecturestudy.R


@BindingAdapter("bind:htmlText")
Copy link
Contributor

Choose a reason for hiding this comment

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

android:text와 같이 기본 attr에 대한 어댑터를 사용할 때는 prefix가 필요하지만 그 경우가 아니라면 bind prefix를 붙일 필요가 없어요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

88f5031
수정했습니다.

fun setHtmlText(textView: TextView, html: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
textView.text = Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT)
} else {
textView.text = Html.fromHtml(html)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

이부분은 HtmlCompat을 써서 버전 분기 없이 한줄로 줄 일 수 있어요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

af549d9
수정했습니다.

}

@BindingAdapter("bind:urlImage")
Copy link
Contributor

Choose a reason for hiding this comment

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

bind: 제거

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fun setUrlImage(imageView: ImageView, url: String) {
Glide.with(imageView.context).load(url)
Copy link
Contributor

Choose a reason for hiding this comment

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

Glide.with를 쓸 때 imageView.context대신 imageView를 넣어주세요.
with를 통해 넘어오는 파라미터의 생명주기에 따라 RequestManager 인스턴스를 자동으로 관리 해주는데 imageView.context를 쓰면 View가 어디에 위치하던지 activity의 생명주기를 따르게 돼요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

1aef5d3
수정했습니다.

.placeholder(R.drawable.ic_default)
.into(imageView)
}

@BindingAdapter("bind:movieRating")
Copy link
Contributor

Choose a reason for hiding this comment

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

bind: 제거

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

@mtjin xml에서는 유지해주세요.
BindingAdapter에서 bind를 붙이지 않았더라도, xml에서는 bind를 붙일 수 있어요.
사실 xml에서는 아무거나 같다붙여도 동작하고, prefix가 없어도 동작해요. bind는 아무의미가 없기때문이죠. xml에서 bind를 붙여주는건 단순히 다른 attr과의 구분을 줘서 가독성을 높이기위해서에요.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

af06d28
수정했습니다.

fun setMovieRating(ratingBar: RatingBar, score: String) {
ratingBar.rating = (score.toFloatOrNull() ?: 0f) / 2
}

/*
확장함수 사용 필기용입니다.
@BindingAdapter("app:movieRating")
fun RatingBar.setMovieRating(score: String) {
rating = (score.toFloatOrNull() ?: 0f) / 2
}
*/