Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.d4rk.androidtutorials.java.ads.managers;

import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
Expand All @@ -12,6 +13,7 @@

import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.viewbinding.ViewBinding;

import com.d4rk.androidtutorials.java.R;
import com.google.android.gms.ads.AdListener;
Expand All @@ -23,6 +25,10 @@
import com.google.android.gms.ads.nativead.NativeAd;
import com.google.android.gms.ads.nativead.NativeAdView;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* Helper class to load AdMob native ads into a container.
*/
Expand Down Expand Up @@ -73,7 +79,7 @@ public static void load(@NonNull Context context,
adView.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
container.getPaddingRight(), container.getPaddingBottom());
container.setPadding(0, 0, 0, 0);
populateNativeAdView(nativeAd, adView);
populateNativeAdView(nativeAd, adView, layoutRes);
container.removeAllViews();
container.addView(adView);
container.requestLayout();
Expand All @@ -92,14 +98,22 @@ public void onAdFailedToLoad(@NonNull LoadAdError loadAdError) {
adLoader.loadAd(adRequest);
}

private static void populateNativeAdView(@NonNull NativeAd nativeAd, @NonNull NativeAdView adView) {
MediaView mediaView = adView.findViewById(R.id.ad_media);
TextView headlineView = adView.findViewById(R.id.ad_headline);
TextView bodyView = adView.findViewById(R.id.ad_body);
Button callToActionView = adView.findViewById(R.id.ad_call_to_action);
ImageView iconView = adView.findViewById(R.id.ad_app_icon);
TextView attributionView = adView.findViewById(R.id.ad_attribution);
AdChoicesView adChoicesView = adView.findViewById(R.id.ad_choices);
private static void populateNativeAdView(@NonNull NativeAd nativeAd,
@NonNull NativeAdView adView,
@LayoutRes int layoutRes) {
ViewBinding binding = tryBind(adView, layoutRes);
if (binding == null) {
Log.w(TAG, "Could not bind native ad view for layout " + layoutRes);
return;
}

MediaView mediaView = getBindingField(binding, "adMedia", MediaView.class);
TextView headlineView = getBindingField(binding, "adHeadline", TextView.class);
TextView bodyView = getBindingField(binding, "adBody", TextView.class);
Button callToActionView = getBindingField(binding, "adCallToAction", Button.class);
ImageView iconView = getBindingField(binding, "adAppIcon", ImageView.class);
TextView attributionView = getBindingField(binding, "adAttribution", TextView.class);
AdChoicesView adChoicesView = getBindingField(binding, "adChoices", AdChoicesView.class);

if (mediaView != null) {
adView.setMediaView(mediaView);
Expand Down Expand Up @@ -166,4 +180,53 @@ private static void populateNativeAdView(@NonNull NativeAd nativeAd, @NonNull Na

adView.setNativeAd(nativeAd);
}

@androidx.annotation.Nullable
private static ViewBinding tryBind(@NonNull NativeAdView adView, @LayoutRes int layoutRes) {
try {
String resourceName = adView.getResources().getResourceEntryName(layoutRes);
String bindingName = toBindingClassName(resourceName);
String fullClassName = adView.getContext().getPackageName() + ".databinding." + bindingName;
Class<?> bindingClass = Class.forName(fullClassName);
Method bindMethod = bindingClass.getMethod("bind", View.class);
return (ViewBinding) bindMethod.invoke(null, adView);
} catch (Resources.NotFoundException | ClassNotFoundException | NoSuchMethodException |
IllegalAccessException | InvocationTargetException e) {
Log.w(TAG, "Failed to create view binding for native ad layout", e);
return null;
}
}

@androidx.annotation.Nullable
private static <T> T getBindingField(@NonNull ViewBinding binding,
@NonNull String fieldName,
@NonNull Class<T> type) {
try {
Field field = binding.getClass().getField(fieldName);
Object value = field.get(binding);
if (type.isInstance(value)) {
Comment on lines +203 to +207

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid reflection on obfuscatable ViewBinding fields

The new NativeAdLoader looks up binding views by reflection (getField("adHeadline"), etc.) instead of using the generated binding classes directly. These field names are not kept by default when R8 minification runs, so in release builds getBindingField will fail to find the obfuscated members and the ad views stay unbound, resulting in empty or missing native ads. The previous findViewById approach was obfuscation-safe. Consider referencing the concrete binding classes or adding appropriate keep rules before relying on reflective lookup.

Useful? React with 👍 / 👎.

return type.cast(value);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
Log.w(TAG, "Unable to access binding field " + fieldName, e);
}
return null;
}

@NonNull
private static String toBindingClassName(@NonNull String resourceName) {
StringBuilder builder = new StringBuilder(resourceName.length());
boolean capitalize = true;
for (int i = 0; i < resourceName.length(); i++) {
char c = resourceName.charAt(i);
if (c == '_') {
capitalize = true;
} else {
builder.append(capitalize ? Character.toUpperCase(c) : c);
capitalize = false;
}
}
builder.append("Binding");
return builder.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ protected void onBindingCreated(@NonNull T binding, @Nullable Bundle savedInstan
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = inflateBinding(inflater, container);
View adView = binding.getRoot().findViewById(R.id.ad_view);
AdUtils.loadBanner(adView);
AdUtils.loadBanner(getAdView(binding));
onBindingCreated(binding, savedInstanceState);
return binding.getRoot();
}

@NonNull
protected abstract View getAdView(@NonNull T binding);

@Override
public void onDestroyView() {
super.onDestroyView();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,17 @@
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;

import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.menu.MenuBuilder;

import com.d4rk.androidtutorials.java.R;
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;

public abstract class BaseActivity extends AppCompatActivity {

@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
View container = findViewById(R.id.container);
if (container != null) {
EdgeToEdgeDelegate.apply(this, container);
}
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@
import com.d4rk.androidtutorials.java.ads.AdUtils;
import com.d4rk.androidtutorials.java.ads.views.NativeAdBannerView;
import com.d4rk.androidtutorials.java.databinding.FragmentAndroidStudioBinding;
import com.d4rk.androidtutorials.java.databinding.ItemPreferenceBinding;
import com.d4rk.androidtutorials.java.databinding.ItemPreferenceCategoryBinding;
import com.d4rk.androidtutorials.java.databinding.ItemPreferenceWidgetOpenInNewBinding;
import com.google.android.gms.ads.AdListener;
import com.google.android.gms.ads.LoadAdError;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.imageview.ShapeableImageView;
import com.google.android.material.shape.CornerFamily;
import com.google.android.material.shape.ShapeAppearanceModel;
import com.google.android.material.textview.MaterialTextView;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
Expand Down Expand Up @@ -375,13 +375,19 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
adView.setNativeAdUnitId(R.string.native_ad_lessons_list_unit_id);
return new AdHolder(adView);
} else if (viewType == TYPE_CATEGORY) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_preference_category, parent, false);
return new CategoryHolder(view);
ItemPreferenceCategoryBinding binding = ItemPreferenceCategoryBinding.inflate(
LayoutInflater.from(parent.getContext()),
parent,
false
);
return new CategoryHolder(binding);
} else {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_preference, parent, false);
return new LessonHolder(view);
ItemPreferenceBinding binding = ItemPreferenceBinding.inflate(
LayoutInflater.from(parent.getContext()),
parent,
false
);
return new LessonHolder(binding);
}
}

Expand Down Expand Up @@ -424,40 +430,36 @@ static class AdHolder extends RecyclerView.ViewHolder {
}

static class LessonHolder extends RecyclerView.ViewHolder {
final MaterialCardView card;
final ShapeableImageView icon;
final MaterialTextView title;
final MaterialTextView summary;
final FrameLayout widgetFrame;
final MaterialButton externalButton;

LessonHolder(@NonNull View itemView) {
super(itemView);
card = (MaterialCardView) itemView;
icon = itemView.findViewById(android.R.id.icon);
title = itemView.findViewById(android.R.id.title);
summary = itemView.findViewById(android.R.id.summary);
widgetFrame = itemView.findViewById(android.R.id.widget_frame);
LayoutInflater.from(itemView.getContext())
.inflate(R.layout.item_preference_widget_open_in_new, widgetFrame, true);
externalButton = widgetFrame.findViewById(R.id.open_in_new);
private final ItemPreferenceBinding binding;
private final ItemPreferenceWidgetOpenInNewBinding widgetBinding;

LessonHolder(@NonNull ItemPreferenceBinding binding) {
super(binding.getRoot());
this.binding = binding;
widgetBinding = ItemPreferenceWidgetOpenInNewBinding.inflate(
LayoutInflater.from(binding.getRoot().getContext()),
binding.widgetFrame,
true
);
}

void bind(Lesson lesson, boolean first, boolean last) {
if (lesson.iconRes != 0) {
icon.setImageResource(lesson.iconRes);
icon.setVisibility(View.VISIBLE);
binding.icon.setImageResource(lesson.iconRes);
binding.icon.setVisibility(View.VISIBLE);
} else {
icon.setVisibility(View.GONE);
binding.icon.setVisibility(View.GONE);
}
title.setText(lesson.title);
binding.title.setText(lesson.title);
if (lesson.summary != null) {
summary.setText(lesson.summary);
summary.setVisibility(View.VISIBLE);
binding.summary.setText(lesson.summary);
binding.summary.setVisibility(View.VISIBLE);
} else {
summary.setVisibility(View.GONE);
binding.summary.setVisibility(View.GONE);
}
boolean showExternalButton = lesson.opensInBrowser && lesson.intent != null;
FrameLayout widgetFrame = binding.widgetFrame;
MaterialButton externalButton = widgetBinding.openInNew;
widgetFrame.setVisibility(showExternalButton ? View.VISIBLE : View.GONE);
externalButton.setVisibility(showExternalButton ? View.VISIBLE : View.GONE);
externalButton.setEnabled(showExternalButton);
Expand Down Expand Up @@ -486,30 +488,30 @@ private void applyCorners(boolean first, boolean last) {
itemView.getResources().getDisplayMetrics());
float dp24 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24,
itemView.getResources().getDisplayMetrics());
ShapeAppearanceModel.Builder builder = card.getShapeAppearanceModel().toBuilder()
ShapeAppearanceModel.Builder builder = binding.lessonCard.getShapeAppearanceModel().toBuilder()
.setTopLeftCorner(CornerFamily.ROUNDED, first ? dp24 : dp4)
.setTopRightCorner(CornerFamily.ROUNDED, first ? dp24 : dp4)
.setBottomLeftCorner(CornerFamily.ROUNDED, last ? dp24 : dp4)
.setBottomRightCorner(CornerFamily.ROUNDED, last ? dp24 : dp4);
card.setShapeAppearanceModel(builder.build());
binding.lessonCard.setShapeAppearanceModel(builder.build());
}
}

static class CategoryHolder extends RecyclerView.ViewHolder {
final MaterialTextView title;
private final ItemPreferenceCategoryBinding binding;

CategoryHolder(@NonNull View itemView) {
super(itemView);
title = itemView.findViewById(android.R.id.title);
CategoryHolder(@NonNull ItemPreferenceCategoryBinding binding) {
super(binding.getRoot());
this.binding = binding;
}

void bind(Category category) {
if (category.iconRes != 0) {
title.setCompoundDrawablesRelativeWithIntrinsicBounds(category.iconRes, 0, 0, 0);
binding.title.setCompoundDrawablesRelativeWithIntrinsicBounds(category.iconRes, 0, 0, 0);
} else {
title.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
binding.title.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0);
}
title.setText(category.title);
binding.title.setText(category.title);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public class AndroidSDK extends UpNavigationActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityAndroidSdkBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); EdgeToEdgeDelegate.apply(this, binding.scrollView);
setContentView(binding.getRoot());
EdgeToEdgeDelegate.apply(this, binding.scrollView);

AdUtils.loadBanner(binding.adViewBottom);
AdUtils.loadBanner(binding.adView);
Expand All @@ -74,7 +75,7 @@ protected void onCreate(Bundle savedInstanceState) {
}

private void createDynamicTable() {
TableLayout tableLayout = binding.cardViewTableLayout.findViewById(R.id.table_layout);
TableLayout tableLayout = binding.tableLayout;
for (AndroidVersion version : androidVersions) {
TableRow row = new TableRow(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,15 @@

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.d4rk.androidtutorials.java.R;
import com.d4rk.androidtutorials.java.ads.AdUtils;
import com.d4rk.androidtutorials.java.databinding.ActivityShortcutsNavigationAndSearchingBinding;
import com.d4rk.androidtutorials.java.databinding.ItemShortcutBinding;
import com.d4rk.androidtutorials.java.ui.components.navigation.UpNavigationActivity;
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;

Expand Down Expand Up @@ -72,15 +70,14 @@ private static class ShortcutsAdapter extends RecyclerView.Adapter<ShortcutsAdap
@NonNull
@Override
public ShortcutHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_shortcut, parent, false);
return new ShortcutHolder(view);
ItemShortcutBinding binding = ItemShortcutBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ShortcutHolder(binding);
}

@Override
public void onBindViewHolder(@NonNull ShortcutHolder holder, int position) {
Shortcut item = items.get(position);
holder.key.setText(item.key);
holder.description.setText(item.description);
holder.bind(item);
}

@Override
Expand All @@ -89,13 +86,16 @@ public int getItemCount() {
}

static class ShortcutHolder extends RecyclerView.ViewHolder {
final TextView key;
final TextView description;
private final ItemShortcutBinding binding;

ShortcutHolder(@NonNull View itemView) {
super(itemView);
key = itemView.findViewById(R.id.shortcut_key);
description = itemView.findViewById(R.id.shortcut_description);
ShortcutHolder(@NonNull ItemShortcutBinding binding) {
super(binding.getRoot());
this.binding = binding;
}

void bind(@NonNull Shortcut shortcut) {
binding.shortcutKey.setText(shortcut.key());
binding.shortcutDescription.setText(shortcut.description());
}
}
}
Expand Down
Loading
Loading