[Background]
프로젝트를 진행하면서 광고를 넣을 필요가 있어서 Google에서 제공하고 있는 광고 SDK인 Admob을 적용하려고 했습니다
그런데 문제가 그냥 평범한 배너형 광고를 사용하게 되면 최상단 최하단 같은 곳에만 적용이 되고 무엇보다 아래처럼 콘텐츠를 가리거나 디자인이랑 어울리지 않고 너무 동떨어진 느낌이 들었어요.
이 문제점을 Google에서도 파악해서 그런지 다른 대체 방안을 내는데 그건 바로 네이티브 광고입니다.
영어로 토종이라는 뜻을 가진 Native 답게 개발자가 직접 광고 UI를 디자인에 맞게 커스텀을 가능하게 만들어 줍니다.
이 글은 멀티 모듈을 다룬 글에서 사용된 프로젝트 위에 추가를 했습니다.
이 글에 사용된 버전들
- Java 17
- Gradle 8.0.2
- kotlin 1.8.22
- admob 22.4.0
[Admob 적용하기]
Admob을 사용할 모듈에 dependencies를 추가해 주고
dependencies {
implementation("com.google.android.gms:play-services-ads:22.4.0")
}
App 모듈에 있는 Manifest에 광고 앱 ID를 추가해 줍니다
주의: 개발 (디버그) 중일 때는 무조건 테스트용 앱 ID 하고 광고 ID를 입력해야 합니다.
출시를 안 했는데 광고를 계속 보는 것으로 수익을 챙기는 부정행위를 막기 위해서입니다.
<application>
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713" />
</application>
출시할 때는 admob에 앱을 등록한 뒤에 콘솔에 있는 앱 설정에서 앱 ID을 가져오면 됩니다
class DefaultApplication : Application() {
override fun onCreate() {
super.onCreate()
MobileAds.initialize(this)
}
}
Admob을 실행시키기 위한 코드로 한 번만 실행되면 되기 때문에 멀티모듈에서는 Application에 넣는 게 맞다고 생각해서 Application에 추가했습니다.
[RecyclerView에 광고 적용하기]
NativeAd를 화면에 넣는 건 매우 간단합니다
<com.google.android.gms.ads.nativead.NativeAdView
android:id="@+id/native_ad_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</NativeAdView>
NativeAdView에 자신의 디자인에 맞게 안에 icon을 어디에 넣을지, 제목을 어디에 넣을지, 별점은 어디에 넣을지 지금까지 화면을 xml로 그린 것처럼 하면 됩니다.
이제 RecyclerView에 적용해 보겠습니다.
RecyclerView에서 광고를 표시할 빈 ViewHolder를 만들기 위해서 일부로 리스트의 첫 번째 index를 빈 QuestionVO를 넣어줬습니다.
val result = mutableListOf(QuestionVO())
result.addAll(response.questions)
adapter.questionList = result
adapter.notifyDataSetChanged()
광고와 콘텐츠를 비교하기 위해 빈 QuestionVO의 id에 "ad"를 넣어줬지만 원한다면 index로 광고를 넣을 위치를 파악할 수 있습니다
data class QuestionVO(
val title: String,
val id: String,
) {
constructor() : this("", "ad")
}
class SimpleListAdapter(
private val listener: ClickListener,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var questionList: List<QuestionVO> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
TYPE_QUESTION -> {
val binding = ListTileBinding.inflate(inflater, parent, false)
SimpleViewHolder(binding)
}
TYPE_AD -> {
val binding = NativeAdBinding.inflate(inflater, parent, false)
AdViewHolder(binding)
}
else -> throw IllegalArgumentException("Invalid viewType")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is SimpleViewHolder -> {
holder.bind(questionList[position])
holder.itemView.setOnClickListener {
listener.onClick(questionList[position].id)
}
}
is AdViewHolder -> holder.bind()
}
}
override fun getItemCount(): Int = questionList.size
override fun getItemViewType(position: Int): Int {
return if (questionList[position].id == "ad") {
TYPE_AD
} else {
TYPE_QUESTION
}
}
companion object {
private const val TYPE_QUESTION = 0
private const val TYPE_AD = 1
}
}
getItemViewType을 이용해서 광고인지 콘텐츠인지 비교해 주고, onCreateViewHolder나 onBindViewHolder에 맞는 ViewHolder를 적용해 주면 됩니다.
class AdViewHolder(
private val binding: NativeAdBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind() {
val context = binding.root.context
val loader =
AdLoader.Builder(context, "ca-app-pub-3940256099942544/2247696110").forNativeAd {
populateNativeAdView(it, binding.nativeAdView)
}.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(adError: LoadAdError) {
Log.d("TESTING ADS", adError.toString())
}
}).withNativeAdOptions(
NativeAdOptions.Builder().build()
).build()
val request = AdRequest.Builder().build()
loader.loadAd(request)
}
private fun populateNativeAdView(nativeAd: NativeAd, adView: NativeAdView) {
adView.headlineView = binding.primary
adView.callToActionView = binding.cta
adView.iconView = binding.icon
adView.starRatingView = binding.ratingBar
(adView.headlineView as TextView).text = nativeAd.headline
if (nativeAd.callToAction == null) {
adView.callToActionView!!.visibility = View.INVISIBLE
} else {
adView.callToActionView!!.visibility = View.VISIBLE
(adView.callToActionView as Button).text = nativeAd.callToAction
}
if (nativeAd.icon == null) {
adView.iconView!!.visibility = View.GONE
} else {
(adView.iconView as ImageView).setImageDrawable(
nativeAd.icon!!.drawable
)
adView.iconView!!.visibility = View.VISIBLE
}
if (nativeAd.starRating == null) {
adView.starRatingView!!.visibility = View.INVISIBLE
} else {
(adView.starRatingView as RatingBar)
.rating = nativeAd.starRating!!.toFloat()
adView.starRatingView!!.visibility = View.VISIBLE
}
adView.setNativeAd(nativeAd)
}
}
ViewHolder에서는 AdLoader을 이용해서 Google 서버로부터 광고를 불러와줍니다.
전에 명시한 것처럼 테스트용 광고 ID을 넣어줘야 합니다.
추가로 adListener로 성공, 실패 시 취할 행동을 정의하고.
adOption을 이용해서 동영상을 넣어줄 수도 있습니다.
다 적용한다면 빈 Viewholder를 넣은 곳에 직접 디자인 광고창이 뜨게 됩니다.
원한다면 광고를 여러 위치에 넣을 수도 있다
val response = uiState.data as QuestionListVO
val result = response.questions.toMutableList()
result.add(1, QuestionVO())
result.add(4, QuestionVO())
adapter.questionList = result
adapter.notifyDataSetChanged()
[마무리]
생각보다 이와 관련된 자료가 없어서 이번 글을 작성해 봤습니다
전체 코드는 여기서 확인 가능합니다.
https://github.com/flash159483/multi-module-navigation
'android' 카테고리의 다른 글
(android/kotlin) Encrypted Shared Preference 적용 + AEADBadTagException 문제 해결 (0) | 2023.11.17 |
---|---|
(android/kotlin) 멀티모듈에서 proguard 설정하는 방법 (0) | 2023.10.26 |
(android/kotlin) 멀티 모듈에서 Google OAuth만으로 로그인 구현해보기 (0) | 2023.09.16 |