먼저 Web View란, App에서 웹브라우저를 이용해 화면을 보여주는 방식을 말합니다. Web View는 아이템을 클릭했을 때, 해당 품목을 구매할 수 있는 링크로 연결하는 용도로 사용할 수 있습니다.
그래서 이번 포스팅에서는 Coupang에 있는 품목들의 이미지를 Glide 라이브러리로 로드하여 App에서 보여주고, 이미지를 클릭하면 해당 품목의 판매 주소로 연결하는 어플리케이션을 제작해보도록 하겠습니다.
① coupang_content라는 이름의 프로젝트를 생성한다.
② SplashActivity를 추가한다.
③ activity_splash.xml 파일에 TextView 태그를 추가한다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Coupang"
android:textStyle="bold"
android:textColor="#ff0000"
android:textSize="40sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
④ SplashActivity로 돌아와서 아래의 내용을 입력한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_splash)
Handler().postDelayed({
startActivity(Intent(this, MainActivity::class.java))
finish()
}, 3000)
}
⑤ Manifest 설정파일도 아래와 같이 변경한다.
<activity
android:name=".SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
</activity>
① layout 디렉토리 하위로 rv_item.xml 파일을 추가하고, layout을 LinearLayout으로 변경한다. 이후 아래의 내용을 입력한다.
<ImageView
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="10dp"/>
<TextView
android:text="text"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
② LinearLayout 컨테이너에 radius를 적용하기 위해 drawable 디렉토리 하위에 radius라는 이름의 Resource File을 추가하고, 아래의 내용을 입력한다.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" android:padding = "10dp">
<solid android:color="#ffffff"></solid>
<corners
android:bottomLeftRadius="40dp"
android:bottomRightRadius="40dp"
android:topLeftRadius="40dp"
android:topRightRadius="40dp"></corners>
<stroke android:width="1dp"
android:color="#BDBDBD"></stroke>
</shape>
③ rv_item.xml 파일의 LinearLayout 컨테이너의 속성을 아래와 같이 수정한다.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@drawable/radius"
android:orientation="vertical">
④ activity_main.xml 파일에 기존 TextView를 지우고, 아래의 내용을 입력한다.
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
⑤ RVAdapter에서 MutableList의 Generics로 사용될 Data Model(클래스)를 생성하자.
data class DataModel (
val url : String = "",
val imageUrl : String = "",
val titleText : String = ""
)
⑥ default 패키지 하위로 RVAdapter 클래스를 생성하고, 아래의 내용을 입력한다.
class RVAdapter(val items: MutableList<DataModel>) : RecyclerView.Adapter<RVAdapter.ViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RVAdapter.ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: RVAdapter.ViewHolder, position: Int) {
holder.bindItems(items[position])
}
override fun getItemCount(): Int {
return items.size
}
inner class ViewHolder(itemView : View) : RecyclerView.ViewHolder(itemView) {
fun bindItems(item : DataModel) {
}
}
}
⑦ MainActivity로 돌아와서 쿠팡의 오늘의 판매자 특가에 있는 품목들을 가져와 items에 추가하자.
>> 쿠팡
private val items = mutableListOf<DataModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
items.add(
DataModel(
"https://www.coupang.com/vp/products/7009279300?vendorItemId=84504715576&sourceType=HOME_GW_PROMOTION&searchId=feed-66ba5afec8bd40028273ac2f136af659-gw_promotion&isAddedCart=",
"https://thumbnail7.coupangcdn.com/thumbnails/remote/292x292ex/image/vendor_inventory/6b9e/2ff17ef838a2900c9e36354ccfdae6acfc51fa582f0de7f5d6892a3198e1.jpg"
"3칸 도자기 반찬통"
)
)
items.add(
DataModel(
"https://www.coupang.com/vp/products/7210559607?vendorItemId=85726867833&sourceType=HOME_GW_PROMOTION&searchId=feed-bb7986983802447fb3fe221a3ea754f2-gw_promotion&isAddedCart=",
"https://thumbnail7.coupangcdn.com/thumbnails/remote/292x292ex/image/vendor_inventory/e437/7b245b61153b377c3c81085bdf50170618182cbf1283cdfbeef846fd0a9b.jpg",
"캐릭터 손잡이 면기 세트"
)
)
items.add(
DataModel(
"https://www.coupang.com/vp/products/7203906005?vendorItemId=85273275254&sourceType=HOME_GW_PROMOTION&searchId=feed-178addcb95454eecb0e166b22b95f32c-gw_promotion&isAddedCart=",
"https://thumbnail9.coupangcdn.com/thumbnails/remote/292x292ex/image/vendor_inventory/00a5/b9e7e57a87a8dbf518b334f1e1f84ab635e710208abe20b6bf30c85225da.jpg",
"추억의 궁중 약과"
)
)
⑧ 이제 Recycler View에 Adapter를 연결하자. items.add 바로 아래에 추가하면 된다.
val recyclerView = findViewById<RecyclerView>(R.id.rv)
recyclerView.adapter = RVAdapter(items)
recyclerView.layoutManager = LinearLayoutManager(this)
⑨ 만약 한 줄에 두 개의 item을 보여주고 싶다면, layoutManager를 아래와 같이 수정하면 된다.
recyclerView.layoutManager = GridLayoutManager(this, 2)
⑩ rv_itme.xml 파일의 ImageView와 TextView에 id 속성을 추가한다.
<ImageView
android:id="@+id/rvImageArea"
<TextView
android:id="@+id/rvTextArea"
⑪ RVAdater 클래스에 bindItems 메서드를 아래와 같이 정의한다.
fun bindItems(item : DataModel) {
val rvText = itemView.findViewById<TextView>(R.id.rvTextArea)
val rvImage = itemView.findViewById<ImageView>(R.id.rvImageArea)
rvText.text = item.titleText
}
웹 이미지를 URL로 가져오기 위해선 Glide 라이브러리를 사용해야 한다.
① 먼저 관련 의존성을 Module 수준의 build.gradle의 dependencies에 추가한다.
implementation("com.github.bumptech.glide:glide:4.12.0")
annotationProcessor("com.github.bumptech.glide:compiler:4.12.0")
② bindItems 메서드에 아래의 내용을 추가한다.
rvText.text = item.titleText
Glide.with(context)
.load(item.imageUrl)
.into(rvImage)
※ Context
안드로이드 애플리케이션에서 말하는 Context는 애플리케이션 환경에 대한 정보를 제공하는 객체를 의미한다. Activity, Service 등 모든 안드로이드 컴포넌트는 Context 객체를 가지고 있다. Context 객체를 활용하면 리소스에 접근하거나, 애플리케이션 정보를 가져올 수 있고, Activity를 실행 및 종료하는 작업을 수행할 수 있다.
③ Context를 RVAdapter로 전달해주기 위해 RVAdapter 클래스를 수정한다.
class RVAdapter(val context : Context, val items: MutableList<DataModel>) : RecyclerView.Adapter<RVAdapter.ViewHolder>(){
④ 이후 MainActivity에서 Recycler View에 Adapter를 연결하는 부분을 아래와 같이 수정한다.
recyclerView.adapter = RVAdapter(baseContext, items)
⑤ Manifest 설정 파일에 가서 인터넷 연결을 허용하는 설정까지 추가해야 웹 이미지 로드가 가능하다.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
⑥ 코드를 실행시켜보면, 이미지와 텍스트가 메인 화면에 잘 나타날 것이다.
⑦ 레이아웃을 보기 좋게 만들어주기 위해 rv_item.xml 파일을 아래와 같이 수정한다.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_margin="10dp"
android:layout_height="180dp"
android:background="@drawable/radius"
android:orientation="vertical">
<ImageView
android:id="@+id/rvImageArea"
android:src="@drawable/ic_launcher_background"
android:layout_width="match_parent"
android:scaleType="fitXY"
android:layout_height="100dp"
android:layout_marginTop="20dp"
android:layout_marginLeft="25dp"
android:layout_marginRight="25dp"/>
<TextView
android:id="@+id/rvTextArea"
android:text="text"
android:textStyle="bold"
android:textSize="15sp"
android:layout_marginTop="20dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</LinearLayout>
⑧ activity_main.xml 파일에서 배경색을 변경할 수도 있다. ConstrainLayout 컨테이너의 속성으로 추가하면 된다.
android:background="#50bcdf"
⑨ 이제 항목들을 복사 붙여넣기한 후 스크롤이 잘 동작하는지 확인해보자.
① RVAdapter 클래스 정의부 최상단에 아래의 내용을 추가한다.
interface ItemClick {
fun onClick(view : View, position : Int)
}
var itemClick : ItemClick? = null
② onBindViewHolder 메서드도 아래와 같이 수정한다.
override fun onBindViewHolder(holder: RVAdapter.ViewHolder, position: Int) {
if(itemClick != null) {
holder?.itemView?.setOnClickListener {
v -> itemClick!!.onClick(v, position)
}
}
holder.bindItems(items[position])
}
③ 아이템을 클릭했을 때, 나와야 할 새로운 Activity를 정의해야 한다. WebView라는 이름의 Activity를 추가하자.
④ activity_web_view.xml 파일에 WebView 태그를 추가한다.
<WebView
android:id="@+id/wv"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
⑤ MainActivity 파일을 아래와 같이 수정한다.
val recyclerView = findViewById<RecyclerView>(R.id.rv)
val rvAdapter = RVAdapter(baseContext, items)
recyclerView.adapter = rvAdapter
rvAdapter.itemClick = object: RVAdapter.ItemClick {
override fun onClick(view: View, position: Int) {
val intent = Intent(baseContext, WebViewActivity::class.java)
intent.putExtra("url", items[position].url)
intent.putExtra("imageUrl", items[position].imageUrl)
intent.putExtra("title", items[position].titleText)
startActivity(intent)
}
}
recyclerView.layoutManager = GridLayoutManager(this, 2)
⑥ WebView Activity 파일에 아래의 내용을 입력한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
val webView = findViewById<WebView>(R.id.wv)
webView.loadUrl(intent.getStringExtra("url").toString())
}
찜하기 기능을 추가하기 위해선 사용자 인증 과정이 필요하다. 파이어베이스 이메일 로그인 기능을 이용하자.
① 파이어베이스 콘솔에 접속하여 coupang-project라는 이름의 프로젝트를 생성한다. 파이어베이스 관련 설정에 대해서는 아래의 링크를 참조하기 바란다.
② JoinActivity라는 이름의 Activity를 추가한다.
③ activity_join.xml 파일의 레이아웃을 LinearLayout으로 변경하고, orientation을 vertical로 변경한다.
android:orientation="vertical"
④ EditText 태그와 Button 태그를 추가한다.
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="50dp"
android:hint="email"
android:layout_marginTop="200dp"
android:layout_marginHorizontal="40dp"/>
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="50dp"
android:hint="password"
android:layout_marginTop="30dp"
android:layout_marginHorizontal="40dp"/>
<Button
android:id="@+id/join"
android:text="회원가입"
android:layout_marginTop="80dp"
android:layout_marginHorizontal="30dp"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
⑤ SplashActivity에서 회원가입 여부에 따라 JoinActivity와 MainActivity로 구분되어 화면이 전환되도록 만들어주자.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
auth = Firebase.auth
if(auth.currentUser?.uid == null) {
Handler().postDelayed({
startActivity(Intent(this, JoinActivity::class.java))
finish()
}, 3000)
}
else {
Handler().postDelayed({
startActivity(Intent(this, MainActivity::class.java))
finish()
}, 3000)
}
}
⑥ 이제 JoinActivity에 회원가입 로직을 추가하자.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_join)
auth = Firebase.auth
val joinBtn = findViewById<Button>(R.id.join)
joinBtn.setOnClickListener {
val email = findViewById<EditText>(R.id.email)
val password = findViewById<EditText>(R.id.password)
auth.createUserWithEmailAndPassword(email.text.toString(), password.text.toString())
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
} else {
Log.w("JoinActivity", "회원가입 실패", task.exception)
}
}
}
}
① activity_web_view.xml 파일을 아래와 같이 수정한다.
<WebView
android:id="@+id/wv"
android:layout_marginTop="50dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/dibs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="찜하기"
android:textStyle="bold"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
② 파이어베이스 콘솔에서 Realtime Database를 생성한다. 자세한 설명은 아래의 링크를 참조하기 바란다.
>> 파이어베이스 Realtime Database 생성하기
③ 사용자가 찜하기 버튼을 누른 DataModel을 Realtime Database에 저장하기 위해 WebViewActivity 파일을 아래와 같이 수정하자.
private lateinit var auth : FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_web_view)
auth = Firebase.auth
val webView = findViewById<WebView>(R.id.wv)
webView.loadUrl(intent.getStringExtra("url").toString())
val url = intent.getStringExtra("url").toString()
val imageUrl = intent.getStringExtra("imageUrl").toString()
val title = intent.getStringExtra("title").toString()
val dibsBtn = findViewById<TextView>(R.id.dibs)
dibsBtn.setOnClickListener {
val database = Firebase.database
val myRef = database.getReference("dibs_ref")
myRef
.child(auth.currentUser!!.uid)
.push()
.setValue(DataModel(url, imageUrl, title))
}
}
쿠팡이 애플리케이션 외부에서 열리는 탓에 찜하기 버튼이 안 나타나는데, 쿠팡 사이트의 특징 때문에 발생하는 문제일 뿐이니 걱정하지 않아도 된다. 이외의 다른 사이트에 대해서는 정상 동작한다. 앞으로 쿠팡 사이트를 연결할 일이 생기면 다른 방식을 사용해야 할 것 같다.
Realtime Database에 저장된 DataModel을 불러오기 위한 새로운 Activity가 필요하다.
① DibsActivity라는 이름의 Activity를 새로 생성하자.
② activity_main.xml 파일에 TextView 태그를 추가하여 찜한 목록을 조회할 수 있게 만들자.
<TextView
android:id="@+id/dibs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:text="찜한 목록"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_marginTop="50dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
③ 이 TextView에 대한 클릭 이벤트 리스너를 등록하자. MainActivity에 아래의 내용을 작성하자.
val dibsBtn = findViewById<TextView>(R.id.dibs)
dibsBtn.setOnClickListener {
val intent = Intent(this, DibsActivity::class.java)
startActivity(intent)
}
④ 이제 Firebase Realtime Database에서 찜한 목록들을 불러와야 한다. DibsActivity 파일에 아래의 내용을 입력한다.
private lateinit var auth: FirebaseAuth
private val dataModels = mutableListOf<DataModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_dibs)
auth = Firebase.auth
val database = Firebase.database
val myRef = database.getReference("dibs_ref")
myRef
.child(auth.currentUser!!.uid)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for(dataModel in snapshot.children) {
dataModels.add(dataModel.getValue(DataModel::class.java)!!)
}
}
override fun onCancelled(error: DatabaseError) {
Log.e("Dibs", "Failed")
}
})
}
⑤ activity_dibs.xml 파일에서 LinearLayout 태그의 속성에 배경색을 MainActivity와 맞춘다. 이후 RecyclerView 태그와 TextView 태그를 추가한다.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textSize="20sp"
android:gravity="center"
android:textStyle="bold"
android:text="내가 찜한 아이템 목록"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_marginTop="50dp"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
⑥ 이제 DibsActivity에 RecyclerView를 적용해주자. Adapter 클래스는 기존의 RVAdapter를 그대로 사용한다.
val recyclerView = findViewById<RecyclerView>(R.id.rv)
val rvAdapter = RVAdapter(this, dataModels)
recyclerView.adapter = rvAdapter
recyclerView.layoutManager = GridLayoutManager(this, 2)
⑦ 변경된 데이터가 RecyclerView에 반영되기 위해서는 아래의 코드를 onDataChange 메서드에 마지막 부분에 추가해야 한다.
override fun onDataChange(snapshot: DataSnapshot) {
for(dataModel in snapshot.children) {
dataModels.add(dataModel.getValue(DataModel::class.java)!!)
}
rvAdapter.notifyDataSetChanged()
}
⑧ 코드를 실행하면 본인이 찜한 아이템이 "내가 찜한 아이템 목록"에 들어가 있는 것을 확인할 수 있다.
큰 도움이 되었습니다, 감사합니다.