androidfacebookkotlinfacebook-ads-apimopub

Crash with MoPubRecyclerAdapter and Facebook Native Ads in ConstraintLayout


Expected

The MoPubRecyclerAdapter is expected to inflate native Facebook RecyclerView cells using a defined ConstraintLayout.

Observed

Error The MoPubRecyclerAdapter is crashing intermittently for ConstraintLayouts created from Facebook native ads. This issue has been noted in the MoPub SDK forum and MoPub Android Mediation GitHub repository.

Log


Fatal Exception: java.lang.ClassCastException: androidx.constraintlayout.widget.ConstraintLayout cannot be cast to android.widget.RelativeLayout
       at com.mopub.nativeads.FacebookAdRenderer$FacebookNativeViewHolder.fromViewBinder(FacebookAdRenderer.java:139)
       at com.mopub.nativeads.FacebookAdRenderer.renderAdView(FacebookAdRenderer.java:58)
       at com.mopub.nativeads.FacebookAdRenderer.renderAdView(FacebookAdRenderer.java:28)
       at com.mopub.nativeads.NativeAd.renderAdView(NativeAd.java:166)
       at com.mopub.nativeads.MoPubStreamAdPlacer.bindAdView(MoPubStreamAdPlacer.java:433)
       at com.mopub.nativeads.MoPubRecyclerAdapter.onBindViewHolder(MoPubRecyclerAdapter.java:424)
       at androidx.recyclerview.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:6781)
       at androidx.recyclerview.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:6823)
       at androidx.recyclerview.widget.RecyclerView$Recycler.tryBindViewHolderByDeadline(RecyclerView.java:5752)
       at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6019)
       at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:286)
       at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:343)
       at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:359)
       at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:366)
       at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:397)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:205)
       at android.app.ActivityThread.main(ActivityThread.java:6991)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:884)

Implementation

facebook_native_ad_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/native_outer_view"
    style="@style/AdContentCardStyle"
    android:layout_width="match_parent"
    android:layout_height="@dimen/cell_content_feed_height"
    android:textDirection="locale">

    <TextView
        android:id="@+id/native_title"
        style="@style/CellCreatorStyle"
        app:layout_constraintBottom_toBottomOf="@+id/guideline"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@id/native_icon_image" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guideline"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintBottom_toTopOf="@id/native_media_view"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/sponsored"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/sponsored"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/guideline" />

    <com.facebook.ads.AdIconView
        android:id="@+id/native_icon_image"
        android:layout_width="@dimen/native_icon_image_dimen"
        android:layout_height="@dimen/native_icon_image_dimen"
        android:paddingRight="@dimen/padding_tiny"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.facebook.ads.MediaView
        android:id="@+id/native_media_view"
        style="@style/AdCellPreviewImageStyle"
        android:contentDescription="@string/native_main_image"
        app:layout_constraintBottom_toTopOf="@id/native_text"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/sponsored" />

    <TextView
        android:id="@+id/native_text"
        style="@style/CellTitleStyle"
        app:layout_constraintBottom_toTopOf="@id/native_cta"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toBottomOf="@id/native_media_view"
        tools:text="@string/learn_more" />

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/native_ad_choices_relative_layout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:gravity="left"
        app:layout_constraintBottom_toBottomOf="@id/native_cta"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="@id/native_cta" />

    <TextView
        android:id="@+id/native_cta"
        style="@style/NativeCtaStyle"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/native_text"
        tools:text="@string/learn_more" />

</androidx.constraintlayout.widget.ConstraintLayout>

SomeFragment.kt

adapter = FeedAdapter(feedViewModel, viewEvent)
moPubAdapter = MoPubRecyclerAdapter(
                    requireActivity(),
                    adapter,
                    MoPubNativeAdPositioning.MoPubServerPositioning())
            moPubAdapter.registerAdRenderer(FacebookAdRenderer(
                    FacebookViewBinder.Builder(fb_native_ad_item)
                            .titleId(native_title)
                            .textId(native_text)
                            .mediaViewId(native_media_view)
                            .adIconViewId(native_icon_image)
                            .adChoicesRelativeLayoutId(native_ad_choices_relative_layout)
                            .advertiserNameId(native_title)
                            .callToActionId(native_cta)
                            .build()))
            val viewBinder = ViewBinder.Builder(native_ad_item)
                    .titleId(native_title)
                    .textId(native_text)
                    .mainImageId(R.id.native_main_image)
                    .iconImageId(native_icon_image)
                    .callToActionId(native_cta)
                    .privacyInformationIconImageId(string.native_privacy_information_icon_image)
                    .build()
            moPubAdapter.registerAdRenderer(FlurryNativeAdRenderer(FlurryViewBinder(Builder(viewBinder))))
            moPubAdapter.registerAdRenderer(MoPubVideoNativeAdRenderer(
                    MediaViewBinder.Builder(fb_native_ad_item)
                            .mediaLayoutId(native_media_view)
                            .iconImageId(native_icon_image)
                            .titleId(native_title)
                            .textId(native_text)
                            .privacyInformationIconImageId(native_ad_choices_relative_layout)
                            .build()))
            moPubAdapter.registerAdRenderer(MoPubStaticNativeAdRenderer(viewBinder))
            moPubAdapter.setContentChangeStrategy(MOVE_ALL_ADS_WITH_CONTENT)
            contentRecyclerView.adapter = moPubAdapter

FeedApater.kt

@ExperimentalCoroutinesApi
class FeedAdapter(val viewModel: FeedViewModel, val viewEvent: FeedViewEvent)
    : PagedListAdapter<Content, FeedAdapter.ViewHolder>(DIFF_CALLBACK) {

    class ViewHolder(private var binding: CellContentBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(viewModel: FeedViewModel, content: Content, onClickListener: OnClickListener) {
            binding.viewModel = viewModel
            binding.data = content
            binding.clickListener = onClickListener
            binding.executePendingBindings()
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = CellContentBinding.inflate(inflater, parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        getItem(position)?.let { content ->
            holder.bind(viewModel, content, createOnClickListener(content, position))
        }
    }

    private fun createOnClickListener(content: Content, position: Int) = OnClickListener { view ->
}

Environments

Libraries

implementation("com.mopub:mopub-sdk-native-static:5.11.1@aar") { transitive = true }
implementation("com.mopub:mopub-sdk-native-video:5.11.1@aar") { transitive = true }
implementation 'com.facebook.android:audience-network-sdk:5.1.0'
implementation 'com.mopub.mediation:facebookaudiencenetwork:5.1.0.0'
implementation 'com.flurry.android:ads:12.1.0@aar'
implementation 'com.flurry.android:analytics:12.1.0@aar'
implementation 'com.mopub.mediation:flurry:11.4.0.0'

Android levels

Devices

Attempted solution

The library versions have been updated for the MoPub SDK to 5.12.0, the Facebook Audience Network to 5.8.0, and Facebook mediation to 5.8.0.0. It is to be determined whether this resolves the above crash.

implementation("com.mopub:mopub-sdk-native-static:5.12.0") { transitive = true }
implementation("com.mopub:mopub-sdk-native-video:5.12.0") { transitive = true }
implementation 'com.facebook.android:audience-network-sdk:5.8.0'
implementation 'com.mopub.mediation:facebookaudiencenetwork:5.8.0.0'

Solution

  • Use RelativeLayout instead of ConstraintLayout

    As MoPub's engineer points out in this GitHub issue, Facebook mediation is not yet compatible with ConstraintLayout.

    AdChoices icon XML view (with the ID native_ad_choices_relative_layout) needs to be a RelativeLayout due to internal changes in the 4.99.0+ version of the Facebook Audience Network SDK. The adapter expects a RelativeLayout here.

    Before

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/native_ad_choices_relative_layout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:gravity="left"
            app:layout_constraintBottom_toBottomOf="@id/native_cta"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toTopOf="@id/native_cta" />
    

    After

        <RelativeLayout
            android:id="@+id/native_ad_choices_relative_layout"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:gravity="left" />
    

    Documentation: Setup Ad Renderers for Native Ads

    Sample: github.com/mopub/mopub-sdk-android