Skip to content

Commit

Permalink
feat!: add addAdEventListener method (#110)
Browse files Browse the repository at this point in the history
Fixes typing for listeners, fixes "only one listener at a time" bug

* feat: add addAdEventListener method
* refactor!: remove onAdEvent method
* feat: add removeAllListeners method
* test(e2e): replace onAdEvent with addAdEventListener
* docs: add migration doc related to onAdEvent

BREAKING CHANGES: AdEvent Listeners have changed how the work.
There is a migration doc! 
The migration doc has code samples with the changes necessary, we hope it is easy - it is supposed to be easy
https://github.com/invertase/react-native-google-mobile-ads/blob/main/docs/migrating-to-v6.mdx
  • Loading branch information
wjaykim authored Apr 22, 2022
1 parent 8e6fd48 commit e842477
Show file tree
Hide file tree
Showing 19 changed files with 474 additions and 197 deletions.
1 change: 1 addition & 0 deletions .spellcheck.dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ firebase-admin
SSV
CP-User
Intellisense
onAdEvent
37 changes: 32 additions & 5 deletions __tests__/interstitial.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InterstitialAd } from '../src';
import { AdEventType, InterstitialAd } from '../src';

describe('Google Mobile Ads Interstitial', function () {
describe('createForAdRequest', function () {
Expand Down Expand Up @@ -34,17 +34,44 @@ describe('Google Mobile Ads Interstitial', function () {
});
});

describe('onAdEvent', function () {
it('throws if handler is not a function', function () {
describe('addAdEventsListener', function () {
it('throws if listener is not a function', function () {
const i = InterstitialAd.createForAdRequest('abc');

// @ts-ignore
expect(() => i.onAdEvent('foo')).toThrowError("'handler' expected a function");
expect(() => i.addAdEventsListener('foo')).toThrowError("'listener' expected a function");
});

it('returns an unsubscriber function', function () {
const i = InterstitialAd.createForAdRequest('abc');
const unsub = i.onAdEvent(() => {});
const unsub = i.addAdEventsListener(() => {});
expect(unsub).toBeDefined();
unsub();
});
});

describe('addAdEventListener', function () {
it('throws if type is not a AdEventType', function () {
const i = InterstitialAd.createForAdRequest('abc');

// @ts-ignore
expect(() => i.addAdEventListener('foo')).toThrowError(
"'type' expected a valid event type value.",
);
});

it('throws if listener is not a function', function () {
const i = InterstitialAd.createForAdRequest('abc');

// @ts-ignore
expect(() => i.addAdEventListener(AdEventType.LOADED, 'foo')).toThrowError(
"'listener' expected a function",
);
});

it('returns an unsubscriber function', function () {
const i = InterstitialAd.createForAdRequest('abc');
const unsub = i.addAdEventListener(AdEventType.LOADED, () => {});
expect(unsub).toBeDefined();
unsub();
});
Expand Down
3 changes: 2 additions & 1 deletion docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
["Displaying Ads via Hook", "/displaying-ads-hook"],
["European User Consent", "/european-user-consent"],
["Common Reasons For Ads Not Showing", "/common-reasons-for-ads-not-showing"],
["Migrating to v5", "/migrating-to-v5"]
["Migrating to v5", "/migrating-to-v5"],
["Migrating to v6", "/migrating-to-v6"]
]
}
46 changes: 21 additions & 25 deletions docs/displaying-ads.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ The call to `createForAdRequest` returns an instance of the [`InterstitialAd`](/
which provides a number of utilities for loading and displaying interstitials.

To listen to events, such as when the advert from the network has loaded or when an error occurs, we can subscribe via the
`onAdEvent` method:
`addAdEventListener` method:

```jsx
import React, { useEffect, useState } from 'react';
Expand All @@ -107,19 +107,15 @@ function App() {
const [loaded, setLoaded] = useState(false);

useEffect(() => {
const eventListener = interstitial.onAdEvent(type => {
if (type === AdEventType.LOADED) {
setLoaded(true);
}
const unsubscribe = interstitial.addAdEventListener(AdEventType.LOADED => {
setLoaded(true);
});

// Start loading the interstitial straight away
interstitial.load();

// Unsubscribe from events on unmount
return () => {
eventListener();
};
return unsubscribe;
}, []);

// No advert ready to show yet
Expand All @@ -138,12 +134,12 @@ function App() {
}
```

The code above subscribes to the interstitial events (via `onAdEvent()`) and immediately starts to load a new advert from
The code above subscribes to the interstitial events (via `addAdEventListener()`) and immediately starts to load a new advert from
the network (via `load()`). Once an advert is available, local state is set, re-rendering the component showing a `Button`.
When pressed, the `show` method on the interstitial instance is called and the advert is shown over-the-top of your
application.

The `onAdEvent` listener also triggers when events inside of the application occur, such as if the user clicks the advert,
You can subscribe to other various events with `addAdEventListener` listener such as if the user clicks the advert,
or closes the advert and returns back to your app. To view a full list of events which are available, view the
[`AdEventType`](/reference/admob/adeventtype) documentation.

Expand Down Expand Up @@ -181,7 +177,7 @@ The call to `createForAdRequest` returns an instance of the [`RewardedAd`](/refe
which provides a number of utilities for loading and displaying rewarded ads.

To listen to events, such as when the advert from the network has loaded or when an error occurs, we can subscribe via the
`onAdEvent` method:
`addAdEventListener` method:

```js
import React, { useEffect, useState } from 'react';
Expand All @@ -199,22 +195,23 @@ function App() {
const [loaded, setLoaded] = useState(false);

useEffect(() => {
const eventListener = rewarded.onAdEvent((type, error, reward) => {
if (type === RewardedAdEventType.LOADED) {
setLoaded(true);
}

if (type === RewardedAdEventType.EARNED_REWARD) {
console.log('User earned reward of ', reward);
}
const unsubscribeLoaded = rewarded.addAdEventListener(RewardedAdEventType.LOADED, () => {
setLoaded(true);
});
const unsubscribeEarned = rewarded.addAdEventListener(
RewardedAdEventType.EARNED_REWARD,
reward => {
console.log('User earned reward of ', reward);
},
);

// Start loading the rewarded ad straight away
rewarded.load();

// Unsubscribe from events on unmount
return () => {
eventListener();
unsubscribeLoaded();
unsubscribeEarned();
};
}, []);

Expand All @@ -234,15 +231,14 @@ function App() {
}
```

The code above subscribes to the rewarded ad events (via `onAdEvent()`) and immediately starts to load a new advert from
The code above subscribes to the rewarded ad events (via `addAdEventListener()`) and immediately starts to load a new advert from
the network (via `load()`). Once an advert is available, local state is set, re-rendering the component showing a `Button`.
When pressed, the `show` method on the rewarded ad instance is called and the advert is shown over-the-top of your
application.

Like Interstitial Ads, the events returns from the `onAdEvent` listener trigger when the user clicks the advert or closes
the advert and returns back to your app. However, an extra `EARNED_REWARD` event can be triggered if the user completes the
advert action. An additional `reward` property is sent with the event, containing the amount and type of rewarded (specified via the dashboard).
An additional `reward` property is sent with the event, containing the amount and type of rewarded (specified via the dashboard).
Like Interstitial Ads, you can listen to the events with the `addAdEventListener` such as when the user clicks the advert or closes
the advert and returns back to your app. However, you can listen to an extra `EARNED_REWARD` event which is triggered when user completes the
advert action. An additional `reward` payload is sent with the event, containing the amount and type of rewarded (specified via the dashboard).

To learn more, view the [`RewardedAdEventType`](/reference/admob/rewardedadeventtype) documentation.

Expand Down
111 changes: 111 additions & 0 deletions docs/migrating-to-v6.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Migrating to v6

## `onAdEvent` Removed

`onAdEvent` had two problems:

- Only one listener could be registered to an ad instance. Attempting to register a second listener replaced previous listeners
- There were type conflicts around the event payload because typescript didn't know which event was sent to the listener

In order to resolve these problems, `onAdEvent` is replaced by `addAdEventListener` and `addAdEventsListener`.

### `addAdEventListener`

With each call to `addAdEventListener`, you add a listener for one specific event type. The event type to listen for is the first argument and your event handler is the second argument. The type event payload type for the handler is automatically inferred from the event type you passed.

```diff
import {
AdEventType,
RewardedAd,
RewardedAdEventType
} from 'react-native-google-mobile-ads';

const rewardedAd = RewardedAd.createForAdRequest('...');
-rewardedAd.onAdEvent((type, error, reward) => {
- if (type === RewardedAdEventType.LOADED) {
- console.log('Ad has loaded');
- }
- if (type === RewardedAdEventType.EARNED_REWARD) {
- console.log('User earned reward of ', reward);
- }
- if (type === AdEventType.ERROR) {
- console.log('Ad failed to load with error: ', error);
- }
-}
+rewardedAd.addAdEventListener(RewardedAdEventType.Loaded, () => {
+ console.log('Ad has loaded');
+});
+rewardedAd.addAdEventListener(RewardedAdEventType.EARNED_REWARD, (reward) => {
+ console.log('User earned reward of ', reward);
+});
+rewardedAd.addAdEventListener(AdEventType.ERROR, (error) => {
+ console.log('Ad failed to load with error: ', error);
+});
```

To unsubscribe from an event, call the function returned from `addAdEventListener`.

```js
const unsubscribe = interstitialAd.addAdEventListener(AdEventType.Loaded, () => {
console.log('Ad has loaded');
});

// Sometime later...
unsubscribe();
```

### `addAdEventsListener`

With `addAdEventsListener`, you can listen for all of event types as legacy `onAdEvent` did. The handler now passes an object with event type and payload. You have to cast the payload to a corresponding type in order to use in typescript.

```diff
import {
AdEventType,
RewardedAd,
RewardedAdEventType,
RewardedAdReward
} from 'react-native-google-mobile-ads';

const rewardedAd = RewardedAd.createForAdRequest('...');
-rewardedAd.onAdEvent((type, error, reward) => {
+rewardedAd.addAdEventsListener(({ type, payload }) => {
if (type === RewardedAdEventType.LOADED) {
console.log('Ad has loaded');
}
if (type === RewardedAdEventType.EARNED_REWARD) {
+ const reward = payload as RewardedAdReward;
console.log(`User earned reward, name: ${reward.name}, amount: ${reward.amount}`);
}
if (type === AdEventType.ERROR) {
+ const error = payload as Error;
console.log('Ad failed to load with error: ', error.message);
}
}
```

To unsubscribe from events, call the function returned from `addAdEventsListener`.

```js
const unsubscribe = interstitialAd.addAdEventsListener(({ type, payload }) => {
console.log('Ad event: ', type, payload);
});

// Sometime later...
unsubscribe();
```

### `removeAllListeners`

You can remove all listeners registered with `addAdEventListener` and `addAdEventsListener` by calling `removeAllListeners` method.

```js
interstitialAd.addAdEventListener(AdEventType.LOADED, () => {
console.log('Ad has loaded');
});
interstitialAd.addAdEventsListener(({ type, payload }) => {
console.log('Ad event: ', type, payload);
});

// Sometime later...
interstitialAd.removeAllListeners();
```
17 changes: 6 additions & 11 deletions e2e/interstitial.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,14 @@ describe('googleAds InterstitialAd', function () {
requestAgent: 'CoolAds',
});

i.onAdEvent(spy);
i.addAdEventListener(googleAds.AdEventType.LOADED, spy);
i.load();
await Utils.spyToBeCalledOnceAsync(spy, 20000);
i.loaded.should.eql(true);

spy.getCall(0).args[0].should.eql('loaded');
});
});

describe('onAdEvent', function () {
describe('addAdEventListener', function () {
it('unsubscribe should prevent events', async function () {
// Ads on Android in CI load a webview and a bunch of other things so slowly the app ANRs.
if (device.getPlatform() === 'android' && global.isCI == true) {
Expand All @@ -61,7 +59,7 @@ describe('googleAds InterstitialAd', function () {

const spy = sinon.spy();
const i = InterstitialAd.createForAdRequest('abc');
const unsub = i.onAdEvent(spy);
const unsub = i.addAdEventListener(googleAds.AdEventType.LOADED, spy);
unsub();
i.load();
await Utils.sleep(2000);
Expand All @@ -78,12 +76,10 @@ describe('googleAds InterstitialAd', function () {

const i = InterstitialAd.createForAdRequest(googleAds.TestIds.INTERSTITIAL);

i.onAdEvent(spy);
i.addAdEventListener(googleAds.AdEventType.LOADED, spy);
i.load();
await Utils.spyToBeCalledOnceAsync(spy, 20000);
i.loaded.should.eql(true);

spy.getCall(0).args[0].should.eql('loaded');
});

it('errors with an invalid ad unit id', async function () {
Expand All @@ -96,12 +92,11 @@ describe('googleAds InterstitialAd', function () {

const i = InterstitialAd.createForAdRequest('123');

i.onAdEvent(spy);
i.addAdEventListener(googleAds.AdEventType.ERROR, spy);
i.load();
await Utils.spyToBeCalledOnceAsync(spy);

spy.getCall(0).args[0].should.eql('error');
const e = spy.getCall(0).args[1];
const e = spy.getCall(0).args[0];
e.code.should.containEql('googleAds/'); // android/ios different errors
});
});
Expand Down
Loading

0 comments on commit e842477

Please sign in to comment.