Web View 사용하기

변현섭·2023년 8월 18일
0

먼저 Web View란, App에서 웹브라우저를 이용해 화면을 보여주는 방식을 말합니다. Web View는 아이템을 클릭했을 때, 해당 품목을 구매할 수 있는 링크로 연결하는 용도로 사용할 수 있습니다.

그래서 이번 포스팅에서는 Coupang에 있는 품목들의 이미지를 Glide 라이브러리로 로드하여 App에서 보여주고, 이미지를 클릭하면 해당 품목의 판매 주소로 연결하는 어플리케이션을 제작해보도록 하겠습니다.

1. 스플래시 화면 구성하기

① coupang_content라는 이름의 프로젝트를 생성한다.

② SplashActivity를 추가한다.

③ activity_splash.xml 파일에 TextView 태그를 추가한다.

  • ConstraintLayout을 이용해 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>

2. Recycler View 구성하기

① 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>
  • corners
    • color: 배경색
    • Radius: 둥근 정도
  • stroke
    • width: 테두리 두께
    • color: 테두리 색

③ 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(클래스)를 생성하자.

  • 데이터 모델을 정의하기 위해 사용하는 클래스는 class 앞에 data를 붙여주어야 하며 소괄호를 사용해야 한다.
  • 또한 val의 타입을 지정해주어야 한다.
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
}

3. 웹 이미지 Load하기

웹 이미지를 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"

⑨ 이제 항목들을 복사 붙여넣기한 후 스크롤이 잘 동작하는지 확인해보자.

4. Web View 구성하기

① 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())
}

5. 찜하기 기능 추가하기

1) 이메일 로그인 기능 추가하기

찜하기 기능을 추가하기 위해선 사용자 인증 과정이 필요하다. 파이어베이스 이메일 로그인 기능을 이용하자.

① 파이어베이스 콘솔에 접속하여 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)
                }
            }
    }
}

2) 찜하기 기능 추가하기

① 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))
    }
}

쿠팡이 애플리케이션 외부에서 열리는 탓에 찜하기 버튼이 안 나타나는데, 쿠팡 사이트의 특징 때문에 발생하는 문제일 뿐이니 걱정하지 않아도 된다. 이외의 다른 사이트에 대해서는 정상 동작한다. 앞으로 쿠팡 사이트를 연결할 일이 생기면 다른 방식을 사용해야 할 것 같다.

3) 찜한 목록 불러오기

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")
                }

            })
    }
  • DataSnapshot은 파이어베이스의 데이터베이스에서 얻은 값을 의미하고, snapshot.children은 DataSnapshot 객체의 하위 데이터를 의미한다.
  • 즉, key 값이 uid인 모든 DataModel을 dataModels 리스트에 add하고 있는 것이다.

⑤ 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 메서드에 마지막 부분에 추가해야 한다.

  • notifyDataSetChanged 메서드를 호출하지 않으면, RecyclerView에 item이 나타나지 않는다.
override fun onDataChange(snapshot: DataSnapshot) {
    for(dataModel in snapshot.children) {
        dataModels.add(dataModel.getValue(DataModel::class.java)!!)
    }
    rvAdapter.notifyDataSetChanged()
}

⑧ 코드를 실행하면 본인이 찜한 아이템이 "내가 찜한 아이템 목록"에 들어가 있는 것을 확인할 수 있다.

profile
Java Spring, Android Kotlin, Node.js, ML/DL 개발을 공부하는 인하대학교 정보통신공학과 학생입니다.

1개의 댓글

comment-user-thumbnail
2023년 8월 18일

큰 도움이 되었습니다, 감사합니다.

답글 달기