Skip to content

Commit d5b5ca0

Browse files
Mobile Ads Developer Relationscopybara-github
authored andcommitted
TemplateView native template minor refactoring.
PiperOrigin-RevId: 760841534
1 parent 53488be commit d5b5ca0

File tree

3 files changed

+213
-31
lines changed

3 files changed

+213
-31
lines changed

source/android-library/app/src/main/java/com/google/android/ads/nativetemplates/TemplateView.java

Lines changed: 96 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,35 @@
2020
import android.graphics.drawable.Drawable;
2121
import android.text.TextUtils;
2222
import android.util.AttributeSet;
23+
import android.util.Log;
2324
import android.view.LayoutInflater;
2425
import android.widget.Button;
2526
import android.widget.FrameLayout;
2627
import android.widget.ImageView;
2728
import android.widget.RatingBar;
2829
import android.widget.TextView;
2930
import androidx.annotation.Nullable;
31+
import androidx.annotation.VisibleForTesting;
3032
import androidx.constraintlayout.widget.ConstraintLayout;
3133
import com.google.android.gms.ads.nativead.MediaView;
3234
import com.google.android.gms.ads.nativead.NativeAd;
3335
import com.google.android.gms.ads.nativead.NativeAdView;
3436
import com.google.unity.ads.R;
3537

36-
/** Base class for a template view. */
38+
/**
39+
* Base class for a template view. Inspired from:
40+
* http://google3/java/com/google/android/libraries/admob/demo/native_templates/NativeTemplatesAndroid/nativetemplates/src/main/java/com/google/android/ads/nativetemplates/TemplateView.java
41+
* (resource ids may differ).
42+
*/
3743
public final class TemplateView extends FrameLayout {
3844

45+
private static final String TAG = TemplateView.class.getSimpleName();
46+
47+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
48+
static final String MEDIUM_TEMPLATE = "medium_template";
49+
50+
private static final String SMALL_TEMPLATE = "small_template";
51+
3952
private int templateType;
4053
private NativeTemplateStyle styles;
4154
private NativeAd nativeAd;
@@ -49,9 +62,7 @@ public final class TemplateView extends FrameLayout {
4962
private MediaView mediaView;
5063
private Button callToActionView;
5164
private ConstraintLayout background;
52-
53-
private static final String MEDIUM_TEMPLATE = "medium_template";
54-
private static final String SMALL_TEMPLATE = "small_template";
65+
private LayoutInflater layoutInflater;
5566

5667
public TemplateView(Context context) {
5768
super(context);
@@ -67,11 +78,68 @@ public TemplateView(Context context, @Nullable AttributeSet attrs, int defStyleA
6778
initView(context, attrs);
6879
}
6980

70-
public TemplateView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
81+
public TemplateView(
82+
Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
83+
super(context, attrs, defStyleAttr, defStyleRes);
84+
initView(context, attrs);
85+
}
86+
87+
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
88+
public TemplateView(
89+
Context context,
90+
@Nullable AttributeSet attrs,
91+
int defStyleAttr,
92+
int defStyleRes,
93+
LayoutInflater layoutInflater) {
7194
super(context, attrs, defStyleAttr, defStyleRes);
95+
this.layoutInflater = layoutInflater;
7296
initView(context, attrs);
7397
}
7498

99+
private void initView(Context context, @Nullable AttributeSet attributeSet) {
100+
if (attributeSet == null) {
101+
return;
102+
}
103+
// See http://shortn/_b5UYcrWluD for more details.
104+
TypedArray typedArray =
105+
context
106+
.getTheme()
107+
.obtainStyledAttributes(
108+
/* set= */ attributeSet,
109+
/* attrs= */ R.styleable.TemplateView,
110+
/* defStyleAttr= */ 0,
111+
/* defStyleRes= */ 0);
112+
if (typedArray == null) {
113+
return;
114+
}
115+
int templateTypeResource = R.styleable.TemplateView_gnt_template_type;
116+
int templateViewResource = R.layout.gnt_medium_template_view;
117+
try {
118+
templateType = typedArray.getResourceId(templateTypeResource, templateViewResource);
119+
} catch (RuntimeException e) {
120+
Log.e(
121+
TAG,
122+
String.format(
123+
"Failed to get template type from attribute resources (templateTypeResource: %d, "
124+
+ "templateViewResource: %d).",
125+
templateTypeResource, templateViewResource),
126+
e);
127+
// Rethrow to let the exception propagate up the stack.
128+
throw e;
129+
} finally {
130+
typedArray.recycle();
131+
}
132+
133+
if (layoutInflater == null) {
134+
setLayoutInflater(context);
135+
}
136+
layoutInflater.inflate(templateType, this);
137+
}
138+
139+
private void setLayoutInflater(Context context) {
140+
layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
141+
}
142+
75143
public NativeTemplateStyle getStyles() {
76144
return styles;
77145
}
@@ -182,6 +250,17 @@ private void applyStyles() {
182250
requestLayout();
183251
}
184252

253+
private boolean areAllViewsInitialized() {
254+
return nativeAdView != null
255+
&& callToActionView != null
256+
&& primaryView != null
257+
&& secondaryView != null
258+
&& tertiaryView != null
259+
&& mediaView != null
260+
&& iconView != null
261+
&& ratingBar != null;
262+
}
263+
185264
private boolean adHasOnlyStore(NativeAd nativeAd) {
186265
String store = nativeAd.getStore();
187266
String advertiser = nativeAd.getAdvertiser();
@@ -191,6 +270,12 @@ private boolean adHasOnlyStore(NativeAd nativeAd) {
191270
public void setNativeAd(NativeAd nativeAd) {
192271
this.nativeAd = nativeAd;
193272

273+
if (!areAllViewsInitialized()) {
274+
// Defensive check against potential NPEs if a view has not been initialized (which is
275+
// expected if the template view was solely constructed from context).
276+
return;
277+
}
278+
194279
String store = nativeAd.getStore();
195280
String advertiser = nativeAd.getAdvertiser();
196281
String headline = nativeAd.getHeadline();
@@ -199,24 +284,22 @@ public void setNativeAd(NativeAd nativeAd) {
199284
Double starRating = nativeAd.getStarRating();
200285
NativeAd.Image icon = nativeAd.getIcon();
201286

202-
String secondaryText;
203-
callToActionView.setText(cta);
204-
205287
nativeAdView.setCallToActionView(callToActionView);
206288
nativeAdView.setHeadlineView(primaryView);
207289
nativeAdView.setMediaView(mediaView);
208290
secondaryView.setVisibility(VISIBLE);
291+
292+
String secondaryText = "";
209293
if (adHasOnlyStore(nativeAd)) {
210294
nativeAdView.setStoreView(secondaryView);
211295
secondaryText = store;
212296
} else if (!TextUtils.isEmpty(advertiser)) {
213297
nativeAdView.setAdvertiserView(secondaryView);
214298
secondaryText = advertiser;
215-
} else {
216-
secondaryText = "";
217299
}
218300

219301
primaryView.setText(headline);
302+
callToActionView.setText(cta);
220303

221304
// Set the secondary view to be the star rating if available.
222305
if (starRating != null && starRating > 0) {
@@ -253,7 +336,9 @@ public void setNativeAd(NativeAd nativeAd) {
253336
* https://developers.google.com/admob/android/native-unified#destroy_ad
254337
*/
255338
public void destroyNativeAd() {
256-
nativeAd.destroy();
339+
if (nativeAd != null) {
340+
nativeAd.destroy();
341+
}
257342
}
258343

259344
public String getTemplateTypeName() {
@@ -265,22 +350,6 @@ public String getTemplateTypeName() {
265350
return "";
266351
}
267352

268-
private void initView(Context context, AttributeSet attributeSet) {
269-
TypedArray attributes =
270-
context.getTheme().obtainStyledAttributes(attributeSet, R.styleable.TemplateView, 0, 0);
271-
272-
try {
273-
templateType =
274-
attributes.getResourceId(
275-
R.styleable.TemplateView_gnt_template_type, R.layout.gnt_medium_template_view);
276-
} finally {
277-
attributes.recycle();
278-
}
279-
LayoutInflater inflater =
280-
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
281-
inflater.inflate(templateType, this);
282-
}
283-
284353
@Override
285354
public void onFinishInflate() {
286355
super.onFinishInflate();

source/android-library/app/src/main/java/com/google/unity/ads/UnityAdSize.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ public static AdSize getPortraitAnchoredAdaptiveBannerAdSize(Activity activity,
3535
return AdSize.getPortraitAnchoredAdaptiveBannerAdSize(activity, width);
3636
}
3737

38-
public static AdSize getSmartBannerAdSize() {
39-
return AdSize.SMART_BANNER;
40-
}
41-
4238
private static int getScreenWidth(Activity activity) {
4339
DisplayMetrics displayMetrics = new DisplayMetrics();
4440
WindowManager windowManager = activity.getWindowManager();
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package com.google.android.ads.nativetemplates;
2+
3+
import static com.google.common.truth.Truth.assertThat;
4+
import static org.mockito.ArgumentMatchers.any;
5+
import static org.mockito.ArgumentMatchers.anyInt;
6+
import static org.mockito.Mockito.never;
7+
import static org.mockito.Mockito.verify;
8+
import static org.mockito.Mockito.when;
9+
10+
import android.content.Context;
11+
import android.content.res.XmlResourceParser;
12+
import android.util.AttributeSet;
13+
import android.util.Xml;
14+
import android.view.LayoutInflater;
15+
import android.view.ViewGroup;
16+
import androidx.test.core.app.ApplicationProvider;
17+
import com.google.android.gms.ads.nativead.NativeAd;
18+
import com.google.unity.ads.R;
19+
import org.junit.After;
20+
import org.junit.Before;
21+
import org.junit.Rule;
22+
import org.junit.Test;
23+
import org.junit.runner.RunWith;
24+
import org.mockito.Mock;
25+
import org.mockito.junit.MockitoJUnit;
26+
import org.mockito.junit.MockitoRule;
27+
import org.robolectric.RobolectricTestRunner;
28+
29+
/** Tests for {@link TemplateView} */
30+
@RunWith(RobolectricTestRunner.class)
31+
public final class TemplateViewTest {
32+
33+
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
34+
@Mock private NativeAd mockNativeAd;
35+
@Mock private LayoutInflater mockLayoutInflater;
36+
37+
private Context context;
38+
private TemplateView templateView;
39+
40+
@Before
41+
public void setUp() {
42+
context = ApplicationProvider.getApplicationContext();
43+
templateView = new TemplateView(context);
44+
}
45+
46+
@After
47+
public void tearDown() {
48+
templateView.destroyNativeAd();
49+
}
50+
51+
@Test
52+
public void templateView_canBeInstantiated() throws Exception {
53+
assertThat(templateView).isNotNull();
54+
assertThat(templateView.getTemplateTypeName()).isEmpty();
55+
assertThat(templateView.getNativeAdView()).isNull();
56+
assertThat(templateView.getBackground()).isNull();
57+
assertThat(templateView.getStyles()).isNull();
58+
}
59+
60+
@Test
61+
public void setNativeAd_withoutViewInit_succeeds() throws Exception {
62+
// Act.
63+
templateView.setNativeAd(mockNativeAd);
64+
65+
// Assert.
66+
assertThat(templateView.getNativeAdView()).isNull();
67+
// Mockito cannot mock final methods. And it cannot detect a final method being called either.
68+
}
69+
70+
@Test
71+
public void setNativeAd_withViewInit_succeeds() throws Exception {
72+
// Act.
73+
templateView =
74+
new TemplateView(
75+
/* context= */ context,
76+
/* attrs= */ null,
77+
/* defStyleAttr= */ 0,
78+
/* defStyleRes= */ 0,
79+
/* layoutInflater= */ mockLayoutInflater);
80+
templateView.setNativeAd(mockNativeAd);
81+
82+
// Assert.
83+
assertThat(templateView.getTemplateTypeName()).isEmpty();
84+
assertThat(templateView.getNativeAdView()).isNull();
85+
assertThat(templateView.getBackground()).isNull();
86+
assertThat(templateView.getStyles()).isNull();
87+
verify(mockLayoutInflater, never()).inflate(anyInt(), any());
88+
}
89+
90+
@Test
91+
public void setNativeAd_withViewInitAndAttributes_succeeds() throws Exception {
92+
// Arrange.
93+
when(mockLayoutInflater.inflate(anyInt(), any())).thenReturn(null);
94+
95+
// Act.
96+
templateView =
97+
new TemplateView(
98+
/* context= */ context,
99+
/* attrs= */ getFakeAttributeSet(),
100+
/* defStyleAttr= */ 0,
101+
/* defStyleRes= */ 0,
102+
/* layoutInflater= */ mockLayoutInflater);
103+
templateView.setNativeAd(mockNativeAd);
104+
105+
// Assert.
106+
assertThat(templateView.getTemplateTypeName()).isEqualTo(TemplateView.MEDIUM_TEMPLATE);
107+
assertThat(templateView.getNativeAdView()).isNull();
108+
assertThat(templateView.getBackground()).isNull();
109+
assertThat(templateView.getStyles()).isNull();
110+
verify(mockLayoutInflater).inflate(anyInt(), any(ViewGroup.class));
111+
}
112+
113+
private AttributeSet getFakeAttributeSet() {
114+
XmlResourceParser parser = context.getResources().getXml(R.layout.gnt_medium_template_view);
115+
return Xml.asAttributeSet(parser);
116+
}
117+
}

0 commit comments

Comments
 (0)