Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(banner): Support maximum height for Inline Adaptive banners #663

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
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
13 changes: 9 additions & 4 deletions RNGoogleMobileAdsExample/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,10 +184,11 @@ class InterstitialTest implements Test {

class BannerTest implements Test {
bannerAdSize: BannerAdSize | string;

constructor(bannerAdSize) {
maxHeight: number | undefined;
markwilcox marked this conversation as resolved.
Show resolved Hide resolved
constructor(bannerAdSize, maxHeight: number | undefined = undefined) {
markwilcox marked this conversation as resolved.
Show resolved Hide resolved
this.bannerAdSize = bannerAdSize;
this.bannerRef = React.createRef();
this.maxHeight = maxHeight;
}

getPath(): string {
Expand All @@ -196,7 +197,7 @@ class BannerTest implements Test {
.map(
s => s.toLowerCase().charAt(0).toUpperCase() + s.toLowerCase().slice(1),
)
.join('');
.join('').concat(this.maxHeight ? `MaxHeight${this.maxHeight}` : '');
}

getTestType(): TestType {
Expand All @@ -214,6 +215,7 @@ class BannerTest implements Test {
: TestIds.BANNER
}
size={this.bannerAdSize}
maxHeight={this.maxHeight}
onPaid={(event: PaidEvent) => {
console.log(
`Paid: ${event.value} ${event.currency} (precision ${
Expand Down Expand Up @@ -994,6 +996,9 @@ class DebugMenuTest implements Test {

// All tests must be registered - a future feature will allow auto-bundling of tests via configured path or regex
Object.keys(BannerAdSize).forEach(bannerAdSize => {
if (bannerAdSize === "INLINE_ADAPTIVE_BANNER") {
TestRegistry.registerTest(new BannerTest(bannerAdSize, 100))
}
TestRegistry.registerTest(new BannerTest(bannerAdSize));
});
TestRegistry.registerTest(new CollapsibleBannerTest());
Expand Down Expand Up @@ -1025,7 +1030,7 @@ TestRegistry.registerTest(new DebugMenuTest());
const App = () => {
return (
<SafeAreaView>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<ScrollView contentContainerStyle={{ paddingRight: 15 }} contentInsetAdjustmentBehavior="automatic">
Copy link
Author

@markwilcox markwilcox Nov 29, 2024

Choose a reason for hiding this comment

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

This was a quick hack because there are too many tests to fit on a screen already and they're all 100% pressable area, so you can't scroll the scroll view. This gives you some space on the right to scroll it. Happy to remove, but I found it helpful.

Copy link
Collaborator

Choose a reason for hiding this comment

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

that has been bugging me for a long long time 😆 - thanks for cleaning that up

<TestRunner />
</ScrollView>
</SafeAreaView>
Expand Down
6 changes: 3 additions & 3 deletions RNGoogleMobileAdsExample/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1514,7 +1514,7 @@ PODS:
- React-logger (= 0.76.2)
- React-perflogger (= 0.76.2)
- React-utils (= 0.76.2)
- RNGoogleMobileAds (14.4.0):
- RNGoogleMobileAds (14.4.2):
- DoubleConversion
- glog
- Google-Mobile-Ads-SDK (= 11.10.0)
Expand Down Expand Up @@ -1814,10 +1814,10 @@ SPEC CHECKSUMS:
React-utils: ed6cb7ba089ac0856aa104df12691e99abbf14e1
ReactCodegen: 93b271af49774429f34d7fd561197020d86436e2
ReactCommon: 208cb02e3c0bb8a727b3e1a1782202bcfa5d9631
RNGoogleMobileAds: e9c8c98e7748e612c4669951336785d2f5ae1937
RNGoogleMobileAds: 9c58ba24a74d300ac88f64363276f33acd1a521a
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: 96872ee462cfc43866ad013c8160d4ff6b85709b

PODFILE CHECKSUM: f699c82614f0340e3985855a1efdaa77a260037e

COCOAPODS: 1.16.1
COCOAPODS: 1.15.2
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,18 @@ public void setSizes(ReactNativeAdView reactViewGroup, ReadableArray value) {
payload.putDouble("height", adSize.getHeight());
sendEvent(reactViewGroup, EVENT_SIZE_CHANGE, payload);
}

reactViewGroup.setSizesArray(value);
reactViewGroup.setSizes(sizeList);
reactViewGroup.setPropsChanged(true);
}

@ReactProp(name = "maxAdHeight")
Copy link
Author

@markwilcox markwilcox Nov 29, 2024

Choose a reason for hiding this comment

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

I wanted to call the native prop "maxHeight" but that ends up setting the native view max height too, which isn't what you want in the case where the user sets a height lower than the minimum (32px) because you get a minimum height ad back and then the view crops it.

public void setMaxAdHeight(ReactNativeAdView reactViewGroup, float value) {
reactViewGroup.setMaxAdHeight(value);
this.setSizes(reactViewGroup, reactViewGroup.getSizesArray());
reactViewGroup.setPropsChanged(true);
}

@ReactProp(name = "manualImpressionsEnabled")
public void setManualImpressionsEnabled(ReactNativeAdView reactViewGroup, boolean value) {
reactViewGroup.setManualImpressionsEnabled(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdSize;
import com.google.android.gms.ads.admanager.AdManagerAdRequest;
import io.invertase.googlemobileads.common.ReactNativeAdView;
import io.invertase.googlemobileads.common.ReactNativeEventEmitter;
import java.util.ArrayList;
import java.util.Map;
Expand All @@ -51,8 +52,16 @@ static AdSize getAdSizeForAdaptiveBanner(String preDefinedAdSize, ViewGroup reac
DisplayMetrics outMetrics = new DisplayMetrics();
display.getMetrics(outMetrics);
int adWidth = (int) (outMetrics.widthPixels / outMetrics.density);
float maxAdHeight = ((ReactNativeAdView)reactViewGroup).getMaxAdHeight();

if ("INLINE_ADAPTIVE_BANNER".equals(preDefinedAdSize)) {
if (maxAdHeight > 0) {
if (maxAdHeight < 32) {
Copy link
Author

Choose a reason for hiding this comment

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

How would you prefer a constant defined for this magic number?

return AdSize.getInlineAdaptiveBannerAdSize(adWidth, 32);
} else {
return AdSize.getInlineAdaptiveBannerAdSize(adWidth, Math.round(maxAdHeight));
}
}
markwilcox marked this conversation as resolved.
Show resolved Hide resolved
return AdSize.getCurrentOrientationInlineAdaptiveBannerAdSize(
reactViewGroup.getContext(), adWidth);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.content.Context;
import android.widget.FrameLayout;
import com.facebook.react.bridge.ReadableArray;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.AdSize;
import java.util.List;
Expand All @@ -18,6 +19,8 @@
public class ReactNativeAdView extends FrameLayout {
private AdRequest request;
private List<AdSize> sizes;
private ReadableArray sizesArray;
private float maxAdHeight;
private String unitId;
private boolean manualImpressionsEnabled;
private boolean propsChanged;
Expand Down Expand Up @@ -74,6 +77,22 @@ public List<AdSize> getSizes() {
return this.sizes;
}

public void setSizesArray(ReadableArray sizesArray) {
this.sizesArray = sizesArray;
}

public ReadableArray getSizesArray() {
return this.sizesArray;
}

public void setMaxAdHeight(float maxAdHeight) {
this.maxAdHeight = maxAdHeight;
}

public float getMaxAdHeight() {
return this.maxAdHeight;
}

public void setUnitId(String unitId) {
this.unitId = unitId;
}
Expand Down
2 changes: 2 additions & 0 deletions ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerComponent.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
@property GADBannerView *banner;
@property(nonatomic, assign) BOOL requested;

@property(nonatomic, copy) NSArray *sizeStrings;
@property(nonatomic, copy) NSArray *sizes;
@property(nonatomic, assign) CGFloat maxAdHeight;
@property(nonatomic, copy) NSString *unitId;
@property(nonatomic, copy) NSDictionary *request;
@property(nonatomic, copy) NSNumber *manualImpressionsEnabled;
Expand Down
15 changes: 14 additions & 1 deletion ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerComponent.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ - (void)setUnitId:(NSString *)unitId {

- (void)setSizes:(NSArray *)sizes {
__block NSMutableArray *adSizes = [[NSMutableArray alloc] initWithCapacity:sizes.count];
_sizeStrings = sizes;
CGFloat maxAdHeight = -1;
if (_maxAdHeight > 0) {
maxAdHeight = _maxAdHeight;
}
[sizes enumerateObjectsUsingBlock:^(id jsonValue, NSUInteger idx, __unused BOOL *stop) {
GADAdSize adSize = [RNGoogleMobileAdsCommon stringToAdSize:jsonValue];
GADAdSize adSize = [RNGoogleMobileAdsCommon stringToAdSize:jsonValue withMaxHeight: maxAdHeight];
if (GADAdSizeEqualToSize(adSize, GADAdSizeInvalid)) {
RCTLogWarn(@"Invalid adSize %@", jsonValue);
} else {
Expand All @@ -78,6 +83,14 @@ - (void)setSizes:(NSArray *)sizes {
_propsChanged = true;
}

- (void)setMaxAdHeight:(CGFloat)maxAdHeight {
_maxAdHeight = maxAdHeight;
if (_sizeStrings != nil) {
Copy link
Collaborator

@dylancom dylancom Dec 17, 2024

Choose a reason for hiding this comment

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

From my testing in the example app, this condition never becomes truthy. So do we really need this extra sizeStrings?
It seems maxAdHeight is always set before sizes.
Android same?

At least in the example app I am always getting this order:
Scherm­afbeelding 2024-12-17 om 11 08 52

Copy link
Author

@markwilcox markwilcox Dec 19, 2024

Choose a reason for hiding this comment

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

Yes, I couldn't reproduce this on iOS or Android, but I was missing the same check on Android initially and running this with ~25k (out of ~700k) Android users in production, I was getting 10s of crashes a day.

My plan is to refactor this to pass an object with the size related props across to native in a single prop, while having separate props exposed on the JS side.

[self setSizes: _sizeStrings];
}
_propsChanged = true;
}

- (void)setRequest:(NSString *)request {
NSData *jsonData = [request dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
Expand Down
1 change: 1 addition & 0 deletions ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, assign) BOOL requested;

@property(nonatomic, copy) NSArray *sizes;
@property(nonatomic, assign) CGFloat maxAdHeight;
@property(nonatomic, copy) NSString *unitId;
@property(nonatomic, copy) NSDictionary *request;
@property(nonatomic, copy) NSNumber *manualImpressionsEnabled;
Expand Down
8 changes: 6 additions & 2 deletions ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,15 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
propsChanged = true;
}

if (oldViewProps.sizes != newViewProps.sizes) {
if (oldViewProps.sizes != newViewProps.sizes || oldViewProps.maxAdHeight != newViewProps.maxAdHeight) {
NSMutableArray *adSizes = [NSMutableArray arrayWithCapacity:newViewProps.sizes.size()];
CGFloat maxAdHeight = -1;
if (newViewProps.maxAdHeight > 0) {
maxAdHeight = newViewProps.maxAdHeight;
}
for (auto i = 0; i < newViewProps.sizes.size(); i++) {
NSString *jsonValue = [[NSString alloc] initWithUTF8String:newViewProps.sizes[i].c_str()];
GADAdSize adSize = [RNGoogleMobileAdsCommon stringToAdSize:jsonValue];
GADAdSize adSize = [RNGoogleMobileAdsCommon stringToAdSize:jsonValue withMaxHeight: maxAdHeight];
if (GADAdSizeEqualToSize(adSize, GADAdSizeInvalid)) {
RCTLogWarn(@"Invalid adSize %@", jsonValue);
} else {
Expand Down
2 changes: 2 additions & 0 deletions ios/RNGoogleMobileAds/RNGoogleMobileAdsBannerViewManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ @implementation RNGoogleMobileAdsBannerViewManager

RCT_EXPORT_VIEW_PROPERTY(sizes, NSArray);

RCT_EXPORT_VIEW_PROPERTY(maxAdHeight, CGFloat);

RCT_EXPORT_VIEW_PROPERTY(unitId, NSString);

RCT_EXPORT_VIEW_PROPERTY(request, NSString);
Expand Down
2 changes: 1 addition & 1 deletion ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
error:(nullable NSDictionary *)error
data:(nullable NSDictionary *)data;

+ (GADAdSize)stringToAdSize:(NSString *)value;
+ (GADAdSize)stringToAdSize:(NSString *)value withMaxHeight:(CGFloat)maxHeight;

+ (BOOL)isAdManagerUnit:(NSString *)unitId;

Expand Down
9 changes: 8 additions & 1 deletion ios/RNGoogleMobileAds/RNGoogleMobileAdsCommon.mm
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ + (void)sendAdEvent:(NSString *)event
[[RNRCTEventEmitter shared] sendEventWithName:event body:payload];
}

+ (GADAdSize)stringToAdSize:(NSString *)value {
+ (GADAdSize)stringToAdSize:(NSString *)value withMaxHeight:(CGFloat)maxHeight {
NSError *error = nil;
NSRegularExpression *regex =
[NSRegularExpression regularExpressionWithPattern:@"([0-9]+)x([0-9]+)"
Expand Down Expand Up @@ -206,6 +206,13 @@ + (GADAdSize)stringToAdSize:(NSString *)value {
}
CGFloat viewWidth = frame.size.width;
if ([value isEqualToString:@"INLINE_ADAPTIVE_BANNER"]) {
if (maxHeight > 0) {
if (maxHeight < 32) {
Copy link
Author

Choose a reason for hiding this comment

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

Again here, how would you prefer a constant defined for the magic number?

return GADInlineAdaptiveBannerAdSizeWithWidthAndMaxHeight(viewWidth, 32.0);
} else {
return GADInlineAdaptiveBannerAdSizeWithWidthAndMaxHeight(viewWidth, maxHeight);
}
}
markwilcox marked this conversation as resolved.
Show resolved Hide resolved
return GADCurrentOrientationInlineAdaptiveBannerAdSizeWithWidth(viewWidth);
}
return GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(viewWidth);
Expand Down
3 changes: 2 additions & 1 deletion src/ads/BaseAd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const sizeRegex = /([0-9]+)x([0-9]+)/;
export const BaseAd = React.forwardRef<
React.ElementRef<typeof GoogleMobileAdsBannerView>,
GAMBannerAdProps
>(({ unitId, sizes, requestOptions, manualImpressionsEnabled, ...props }, ref) => {
>(({ unitId, sizes, maxHeight, requestOptions, manualImpressionsEnabled, ...props }, ref) => {
const [dimensions, setDimensions] = useState<(number | DimensionValue)[]>([0, 0]);

const debouncedSetDimensions = debounce(setDimensions, 100);
Expand Down Expand Up @@ -168,6 +168,7 @@ export const BaseAd = React.forwardRef<
<GoogleMobileAdsBannerView
ref={ref}
sizes={sizes}
maxAdHeight={maxHeight}
style={style}
unitId={unitId}
request={JSON.stringify(validatedRequestOptions)}
Expand Down
1 change: 1 addition & 0 deletions src/ads/GoogleMobileAdsBannerViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type NativeEvent = {

export interface NativeProps extends ViewProps {
sizes: string[];
maxAdHeight?: Float;
unitId: string;
request: string;
manualImpressionsEnabled: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/types/BannerAdProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface BannerAdProps {
*/
size: BannerAdSize | string;

maxHeight?: number;
markwilcox marked this conversation as resolved.
Show resolved Hide resolved
/**
* The request options for this banner.
*/
Expand Down
Loading