Search code examples
javaandroidandroid-recyclerviewfacebook-ads-api

Facebook Native Ads with RecyclerAdapter


I'm trying to implement Facebook Native Ads by including them as an alternative view in my RecyclerAdapter. Has anyone had any experience with this? I've included my entire class for good measure.

public class FeedRecyclerAdapter extends LoadingRowRecyclerAdapter implements AdListener {

    private static final int VIEW_TYPE_FEED = 1;
    private static final int VIEW_TYPE_AD = 2;

    private final Context context;
    private final List<Feed> feedItems;
    private final ImageLoader feedItemImageLoader;
    private FeedItemClickListener feedItemClickListener;
    private boolean isLongPressed = false;

    private NativeAd nativeAd;
    private NativeAdsManager manager;
    View adView;

    public FeedRecyclerAdapter(Context context, List<Feed> feedItems, ImageLoader feedItemImageLoader, NativeAd nativeAd, NativeAdsManager manager) {
        this.context = context;
        this.feedItems = feedItems;
        this.feedItemImageLoader = feedItemImageLoader;
        this.nativeAd = nativeAd;
        this.manager = manager;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case VIEW_TYPE_FEED:
                return new FeedViewHolder(new FeedItemView(context));
            case VIEW_TYPE_AD:
                return new AdditionalHolder(LayoutInflater.from(context).inflate(R.layout.ad_test3, parent, false));
        }

        return super.onCreateViewHolder(parent, viewType);
    }

    public class AdditionalHolder extends RecyclerView.ViewHolder {
        protected LinearLayout templateContainer;
        protected ImageView blank_holder;
        private LinearLayout nativeAdContainer;
        private LinearLayout adView;
        private AdChoicesView adChoicesView;
        ImageView nativeAdIcon;
        TextView nativeAdTitle;
        TextView nativeAdBody;
        MediaView nativeAdMedia;
        TextView nativeAdSocialContext;
        Button nativeAdCallToAction;

        public AdditionalHolder(View view) {
            super(view);

            //// Add ad into the ad container.
            nativeAdContainer = (LinearLayout) view.findViewById(R.id.ad_unit);
            LayoutInflater inflater = LayoutInflater.from(context);
            adView = (LinearLayout) inflater.inflate(R.layout.ad_test3, nativeAdContainer, false);
            nativeAdContainer.addView(adView);

            // Create native UI using the ad metadata.
            nativeAdIcon = (ImageView) view.findViewById(R.id.native_ad_icon);
            nativeAdTitle = (TextView) view.findViewById(R.id.native_ad_title);
            nativeAdBody = (TextView) view.findViewById(R.id.native_ad_body);
            nativeAdMedia = (MediaView) view.findViewById(R.id.native_ad_media);
            nativeAdSocialContext = (TextView) view.findViewById(R.id.native_ad_social_context);
            nativeAdCallToAction = (Button) view.findViewById(R.id.native_ad_call_to_action);

        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        if (getItemViewType(position) == VIEW_TYPE_FEED) {
            bindFeedItemView((FeedViewHolder) viewHolder, position);
        } else if (getItemViewType(position) == VIEW_TYPE_AD) {

            AdditionalHolder new_holder = (AdditionalHolder) viewHolder;
            try {
                new_holder.templateContainer.removeViewInLayout(adView);
            } catch (Exception e) {
                e.printStackTrace();
            }
            nativeAd = manager.nextNativeAd();
            try {
                adView = NativeAdView.render(context, nativeAd, NativeAdView.Type.HEIGHT_300);
            } catch (NullPointerException e) {
                e.printStackTrace();
            }

            new_holder.templateContainer.addView(adView);
            new_holder.blank_holder.setVisibility(View.GONE);

            // Setting the Text.
            new_holder.nativeAdSocialContext.setText(nativeAd.getAdSocialContext());
            new_holder.nativeAdCallToAction.setText(nativeAd.getAdCallToAction());
            new_holder.nativeAdTitle.setText(nativeAd.getAdTitle());
            new_holder.nativeAdBody.setText(nativeAd.getAdBody());

            // Downloading and setting the ad icon.
            NativeAd.Image adIcon = nativeAd.getAdIcon();
            NativeAd.downloadAndDisplayImage(adIcon, new_holder.nativeAdIcon);

            // Download and setting the cover image.
            NativeAd.Image adCoverImage = nativeAd.getAdCoverImage();
            new_holder.nativeAdMedia.setNativeAd(nativeAd);

            // Add adChoices icon
            if (new_holder.adChoicesView == null) {
                new_holder.adChoicesView = new AdChoicesView(context, nativeAd, true);
                new_holder.adView.addView(new_holder.adChoicesView, 0);
            }

            nativeAd.registerViewForInteraction(new_holder.adView);

        }
    }

    // Differentiate between feedItem views and nativeAds
    @Override
    public int getItemViewType(int position) {
        int viewType = 1;
        if ((position % 9 == 0) && position != 1) viewType = 2;
        return viewType;
    }

    // For feedItems
    private void bindFeedItemView(FeedViewHolder viewHolder, int position) {
        final FeedItemView feedItemView = viewHolder.feedItemView;
        final Feed feedDesign = feedItems.get(position);

        // TODO we can probably conditionally show or hide these based on the type of feed item, same as in FeedActivitySingle
        feedItemView.showOrHideEditButton(true);
        feedItemView.showOrHideBuyButton(false);
        feedItemView.showOrHideFlipButton(false);
        feedItemView.showOrHidePriceText(false);

        // Set the results into TextViews
        feedItemView.setProductPriceText(String.valueOf(feedDesign.getDesign().getPrice()));
        feedItemView.setDownloadsText(String.valueOf(feedDesign.getDesign().getDownloadCount()));
        feedItemView.setLikesText(String.valueOf(feedDesign.getDesign().getLikesCount()));
        feedItemView.setUsernameText(feedDesign.getDesign().getAuthor().getUsername());
        feedItemView.setTimestampText(feedDesign.getTimestampText());
        feedItemView.getSaveImage().setImageResource(feedDesign.isInPersonalGallery() ? R.drawable.ic_action_saved : R.drawable.ic_not_saved);
        feedItemView.getLikeImage().setImageResource(feedDesign.isLiked() ? R.drawable.ic_action_like_feed_full : R.drawable.ic_action_like_feed);

        feedItemView.getTrashImage().setVisibility(ParseHelper.isCurrentUser(feedDesign.getDesign().getAuthor().getObjectId()) ? View.VISIBLE : View.GONE);
        feedItemView.getFeedSocialShareImage().setVisibility(View.VISIBLE);

        feedItemImageLoader.DisplayImage(feedDesign.getDesign().getCompressedImage().getUrl(), feedItemView.getImage(), feedItemView.getProgressBar());

        if(feedDesign.getDesign().getAuthor().getProfilePicture() != null) {
            feedItemImageLoader.DisplayImage(feedDesign.getDesign().getAuthor().getProfilePicture().getUrl(), feedItemView.getProfilePicture(), null); // TODO should this use profilePictureFileCache?
        } else {
            viewHolder.feedItemView.getProfilePicture().setImageResource(R.drawable.ic_anonymous);
        }

        SetCommentViews(feedItemView, feedDesign.getComments());
        SetClickListeners(feedItemView, feedDesign);
    }

    @Override
    protected int getContentDataSize() {
        return feedItems.size();
    }

    @Override
    protected int getViewType(int position) {
        if (position == 1) {
            return VIEW_TYPE_FEED;
        } else {
            return VIEW_TYPE_AD;
        }
    }

    // Facebook Ad methods
    @Override
    public void onError(Ad ad, AdError adError) {

    }

    @Override
    public void onAdLoaded(Ad ad) {

    }

    @Override
    public void onAdClicked(Ad ad) {

    }

    public void setFeedItemClickListener(FeedItemClickListener feedItemClickListener) {
        this.feedItemClickListener = feedItemClickListener;
    }

    private void SetCommentViews(FeedItemView feedItemView, List<Comment> comments) {

        feedItemView.getViewAllComments().setVisibility(View.VISIBLE);
        feedItemView.setViewAllComments("View more comments");

        // reset layouts to hidden
        feedItemView.getComment1Layout().setVisibility(View.GONE);
        feedItemView.getComment2Layout().setVisibility(View.GONE);
        feedItemView.getComment3Layout().setVisibility(View.GONE);

        if (comments == null || comments.size() == 0) {
            feedItemView.getViewAllComments().setVisibility(View.GONE);
            return;
        }

        // note these are meant to fall-through without break statements
        switch (comments.size()) {
            case 3:
                feedItemView.getComment3Layout().setVisibility(View.VISIBLE);
                feedItemView.setCommentUser3(comments.get(2).getAuthor().getUsername());
                feedItemView.setCommentText3(comments.get(2).getComment());
            case 2:
                feedItemView.getComment2Layout().setVisibility(View.VISIBLE);
                feedItemView.setCommentUser2(comments.get(1).getAuthor().getUsername());
                feedItemView.setCommentText2(comments.get(1).getComment());
            case 1:
                feedItemView.getComment1Layout().setVisibility(View.VISIBLE);
                feedItemView.setCommentUser1(comments.get(0).getAuthor().getUsername());
                feedItemView.setCommentText1(comments.get(0).getComment());
                break;
        }
    }

    private void SetClickListeners(final FeedItemView feedItemView, final Feed feedDesign) {

        // setup gesture detector with current view, view holder, and feed design
        final GestureDetector gestureDetector = new GestureDetector(context, new GestureListener(feedDesign, feedItemView));

        // set gesture detector on image view
        feedItemView.setImageTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (motionEvent.getAction() == ACTION_UP){
                    if (isLongPressed){
                        isLongPressed = false;
                        feedItemClickListener.onMarkteFeedImageLongPressed_Release();
                        Log.d("anyTAG","Up");
                        return true;
                    }
                }
                if (motionEvent.getAction() == ACTION_MOVE){
                    return true;
                }
                return gestureDetector.onTouchEvent(motionEvent);
            }
        });

        feedItemView.setTrashImageClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onTrashClicked(feedDesign);
            }
        });

        feedItemView.setFeedSocialShareClickListener(v -> {
            if (feedItemClickListener != null) {
                try {
                    feedItemClickListener.onSocialShareClicked(feedDesign);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

        feedItemView.setLikeImageClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onLikeClicked(feedDesign, feedItemView);
            }
        });

        feedItemView.setSaveImageClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onSaveClicked(feedDesign, feedItemView);
            }
        });

        feedItemView.setAuthorClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onAuthorClicked(feedDesign);
            }
        });

        feedItemView.setCommentTextClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onCreateCommentClicked(feedDesign);
            }
        });

        feedItemView.setViewAllCommentsClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onCreateCommentClicked(feedDesign);
            }
        });

        feedItemView.setEditImageClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onCreateEditImageClicked(feedDesign);
            }
        });

        feedItemView.setCommentImageClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onCreateCommentClicked(feedDesign);
            }
        });

        feedItemView.setCommentUser1ClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onAuthorClicked(feedDesign.getComments().get(0).getAuthor().getObjectId());
            }
        });

        feedItemView.setCommentUser2ClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onAuthorClicked(feedDesign.getComments().get(1).getAuthor().getObjectId());
            }
        });

        feedItemView.setCommentUser3ClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onAuthorClicked(feedDesign.getComments().get(2).getAuthor().getObjectId());
            }
        });

        feedItemView.setProfilePictureClickListener(v -> {
            if (feedItemClickListener != null) {
                feedItemClickListener.onProfilePictureClicked(feedDesign);
            }
        });

    }

    class FeedViewHolder extends RecyclerView.ViewHolder {

        FeedItemView feedItemView;

        public FeedViewHolder(FeedItemView view) {
            super(view);
            this.feedItemView = view;
        }

    }

    // Gesture detector for double tap
    private class GestureListener extends GestureDetector.SimpleOnGestureListener {

        private Feed feedDesign;
        private FeedItemView feedItemView;

        public GestureListener(Feed feedDesign, FeedItemView feedItemView) {
            this.feedDesign = feedDesign;
            this.feedItemView = feedItemView;
        }

        @Override
        public boolean onDown(MotionEvent e) {
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent event) {
            Log.d("FeedRecyclerAdapter", feedDesign.getDesign().getCompressedImage().getUrl());

            if (feedItemClickListener != null) {
                feedItemClickListener.onImageDoubleTapped(feedDesign, feedItemView);
            }

            return true;
        }

        @Override
        public void onLongPress(MotionEvent event) {

            if (event.getAction() == ACTION_DOWN)
            {
                if (!isLongPressed){
                    isLongPressed = true;
                    feedItemClickListener.onMarketFeedImageLongPressed(feedDesign, feedDesign.getDisplayedSide());
                }
            }
        }
    }
}

I'm getting the following exception:

07-01 16:32:12.509 23168-23168/com.elgami.customizer E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.elgami.customizer, PID: 23168
java.lang.NullPointerException: Attempt to invoke virtual method 'com.facebook.ads.NativeAd com.facebook.ads.NativeAdsManager.nextNativeAd()' on a null object reference
at com.elgami.feed.FeedRecyclerAdapter.onBindViewHolder(FeedRecyclerAdapter.java:121)
at android.support.v7.widget.RecyclerView$Adapter.onBindViewHolder(RecyclerView.java:5768)
at android.support.v7.widget.RecyclerView$Adapter.bindViewHolder(RecyclerView.java:5801)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:5037)
at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:4913)
at android.support.v7.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2029)
at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1414)
at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1377)
at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:578)
at android.support.v7.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:3260)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:3069)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3518)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:598)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1079)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1743)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1586)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1495)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:336)
at android.widget.FrameLayout.onLayout(FrameLayout.java:273)
at com.android.internal.policy.PhoneWindow$DecorView.onLayout(PhoneWindow.java:2678)
at android.view.View.layout(View.java:16636)
at android.view.ViewGroup.layout(ViewGroup.java:5437)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2171)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1931)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1107)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6013)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:858)
at android.view.Choreographer.doCallbacks(Choreographer.java:670)
at android.view.Choreographer.doFrame(Choreographer.java:606)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:844)
at android.os.Ha

To begin with, it seems to be complaining about this line:

nativeAd = manager.nextNativeAd();

Here is a gist of my FeedActivity class:

https://gist.github.com/anonymous/bd1b6afd21d27744a99970d3f51aa8a0


Solution

  • A NullPointerException occurs when you try to call a method or access a field of an object reference, but that reference does not hold a valid object. A simple example would be:

    String someString;
    char firstChar = someString.charAt(0); // ERROR!
    

    This code throws an error because the someString variable does not reference a valid String instance when charAt is called. Notice that the first line does not create a String instance, it only declares a variable that may point to a String. In fact, this code is equivalent to:

    String someString = null;
    char firstChar = someString.charAt(0);
    

    For more information about NullPointerExceptions, why they occur and how you can fix them: see this question.

    In your case, the error is caused by line 176 in FeedActivity:

    feedRecyclerAdapter = new FeedRecyclerAdapter(this, feedItems, new ImageLoader(new FeedItemFileCache(this)), nativeAd, manager);
    

    Here, manager is an instance field of the FeedActivity class, defined on line 87:

    public NativeAdsManager manager;
    

    The problem is that this field is never assigned a value: there are no assignments to this field (such as manager = ...). As such, when line 176 is executed, the FeedRecyclerAdapter receives null as its manager argument, and throws a NullPointerException later on in onBindViewHolder when it tries to use that manager.

    The solution is to create a valid NativeAdsManager in your FeedActivity and assign it to manager before passing it to FeedRecyclerAdapter. From the Facebook documentation about NativeAdsManager, such code should look like this:

    public class YourActivity extends Activity implements NativeAdsManager.Listener {
    
        private NativeAdsManager manager; 
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_layout);
    
            manager = new NativeAdsManager(this, "YOUR_PLACEMENT_ID", 5);
            manager.setListener(this);
            manager.loadAds();
    
        }
    }