Rendering Admob Banner within react native flatlist items - react-native-firebase

I have integrated react-native-firebase and the AdMob module into my react native project and everything is working as expected (test ads and ads in production). I now want to add banners within my Flatlist (rendering a banner every X number of rows).
this is an example of the data I'm receiving from the server:
[
{
...item 1 details
},{
...item 2 details
},{
...ad details
},{
...item 3 details
},{
...item 4 details
},{
...ad details
}
]
and this is the Flatlist component
class ItemsList extends React.Component {
_renderItem = ({item, index}) => {
if (item.isAd) {
const unitId = Platform.OS === "ios" ? item.adIdIOS : item.adIdAndroid;
const Banner = firebase.admob.Banner;
const AdRequest = firebase.admob.AdRequest;
const request = new AdRequest();
const keyWords = item.admobKeywords || [];
keyWords.forEach(keyword => {
request.addKeyword(keyword);
});
return (
<View style={{ width:"100%", height: item.adHeight || "auto", marginTop: 5, marginBottom: 15, alignItems: 'center', justifyContent: 'center' }}>
<Banner
size={item.adSize}
request={request.build()}
unitId={unitId}
onAdLoaded={() => {}}
/>
</View>
)
} else {
renderItem()
}
}
render() {
return(
<View>
<FlatList
data={this.state.data}
renderItem={this._renderItem}
...
/>
</View>
)
};
};
the problem is that whenever a scroll event happens (user scrolls down), the Flatlist keeps re-rendering the banner (firing a new request) and the banner keeps refreshing. After a while, a lot of requests get fired and the application crashes!
I know that calling a network request inside a renderItem method is not a good practice so I also tried the following but the problem is still the same:
const Banner = firebase.admob.Banner;
const AdRequest = firebase.admob.AdRequest;
const request = new AdRequest();
class CatalogueSalesList extends React.Component {
_renderItem = ({item, index}) => {
if (item.isAd) {
const unitId = Platform.OS === "ios" ? item.adIdIOS : item.adIdAndroid;
const keyWords = item.admobKeywords || [];
keyWords.forEach(keyword => {
request.addKeyword(keyword);
});
return (
<View style={{ width:"100%", height: item.adHeight || "auto", marginTop: 5, marginBottom: 15, alignItems: 'center', justifyContent: 'center' }}>
<Banner
size={item.adSize}
request={request.build()}
unitId={unitId}
onAdLoaded={() => {}}
/>
</View>
)
} else {
renderItem()
}
}
render() {
return(
<View>
<FlatList
data={this.state.data}
renderItem={this._renderItem}
...
/>
</View>
)
};
};
Can someone please provide me with a solution to how a banner ad could be implemented inside a Flatlist without the banner getting re-rendered every time the user scrolls through the list?
the version that I'm using:
"react-native": "0.59.9",
"react-native-firebase": "~5.5.3"

Flatlist component contains onScroll event hooks you can use to determine when and what type of scrolling is happening:
onScrollBeginDrag, onScrollEndDrag,onMomentumScrollBegin, onMomentumScrollEnd
Use these and make your banner component/list item a stateful component.
The code below will render your admob stuff (and it's associated network requests) only when user is not scrolling and only once.
If your banner component is straight from admob, then wrap it in a stateful component and do the same as below.
List Parent Component
class CatalogueSalesList extends React.Component {
constructor() {
this.state = {
isDragging: false,
isGliding: false,
}
_renderItem = ({item, index}) => {
return (
<View>
<Banner
isDragging={this.state.isDragging}
isGliding={this.state.isGliding}
size={item.adSize}
request={request.build()}
unitId={unitId}
onAdLoaded={() => {}}
/>
</View>
)
} else {
renderItem()
}
}
render() {
return(
<View>
<FlatList
onScrollBeginDrag={
() => {this.setState({isDragging: true});}
}
onScrollEndDrag={
() => {this.setState({isDragging: false});}
}
onMomentumScrollBegin={
() => {this.setState({isGliding: true});}
}
onMomentumScrollEnd={
() => {this.setState({isGliding: false});}
}
data={this.state.data}
renderItem={this._renderItem}
...
/>
</View>
)
};
};
Banner.tsx
class Banner extends React.Component {
constructor(props) {
super(props)
this.state = {
shouldRenderAd: false,
}
}
componentDidMount() {
if (!this.props.isGliding && !this.props.isDragging) {
this.setState({
shouldRenderAd: true,
});
}
}
shouldComponentUpdate(nextProps: Props): boolean {
if (this.state.shouldRenderAd) {
// if it has already rendered the ad, don't re-render
return false;
} else if (!nextProps.isDragging && !nextProps.isGliding) {
// if stationary, ok to re-render
return true;
} else if (nextProps.isDragging && !nextProps.isGliding) {
// if dragging, but not gliding, ok to re-render
return true;
} else {
// otherwise (it is gliding) don't re-render
return false;
}
}
componentDidUpdate() {
if (!this.props.isGliding && !this.props.isDragging) {
// if no scrolling is happening
this.setState({
shouldRenderAd: true,
});
}
}
render() {
if (this.state.shouldRenderAd) {
return (
// render your admob (network requests) stuff here
);
} else {
return (
// Placeholder component for your ad
// probably an empty view the same dimensions as the ad banner
);
}
}
}

Related

NextJS shallow routing breaks anchor tags

In a next.js app, I have a card detail page that takes [id] as a parameter. By design, if an invalid id is passed (e.g. /card/pick-a-new-card), then getServerSideProps picks a random card instead. As suggested in the docs, I'm using shallow routing to then update the URL:
// /card/[id].tsx
useEffect(() => {
router.push(`/card/${card?.id}`, undefined, { shallow: true })
});
This works just fine. Except that it breaks my home button, which is rendered very plainly by:
<Button color="inherit" href="/">Home</Button>
Now this home button no longer works -- nothing appears to happen after one clicks it.
For now, I've worked around the issue with the old school solution of
window.history.pushState(null, '', `/card/${card?.id}`);
But I have no idea what about the shallow route is killing the home button -- that behavior seems very concerning to me.
So far, I have tried these things with no change in behavior:
using <Link> instead of a button.
using a navigation script in onClick instead of a simple href.
Here's the complete id.tsx page:
import { Card, Deck } from '#prisma/client';
import type { GetServerSideProps, NextPage } from 'next'
import { useRouter } from 'next/router';
import { MyAppBar } from '../../components/MyAppBar';
import { prisma } from '../../lib/prisma';
import { Button, Card as MuiCard, CardActions, CardContent, Container, Typography } from '#mui/material';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import useAlert from '../../components/useAlert';
import { formatLastUsedDate } from '../../lib/utils';
const CardPage: NextPage<{ card: Card | null, deck: Deck }> = ({ card, deck }) => {
const router = useRouter();
const { setAlert } = useAlert();
// update the URL
useEffect(() => {
// TODO: shallow routing sometimes kills all link navigation
// router.push(`/card/${card?.id}`, undefined, { shallow: true });
window.history.pushState(null, '', `/card/${card?.id}`);
});
// disable the button if it's just been clicked
const [justClicked, setJustClicked] = useState(false);
const refreshData = () => {
router.replace(router.asPath)
}
async function markUsed(cardId: number) {
const used = dayjs();
const data = { id: cardId, dateUsed: used.toISOString() }
try {
fetch(`/api/card/${cardId}`, {
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
method: 'POST'
}).then(() => {
setAlert("Complete.", "success");
// TODO: why is this necessary to refresh the ui?
if (card !== null) { card.dateUsed = used.toDate() }
});
} catch (error) {
setAlert("An error occurred while updating the record.", "error");
}
}
return (
<Container maxWidth='md' sx={{ paddingLeft: { xs: 0, sm: 0 }, paddingRight: { xs: 0, sm: 0 } }}>
<MyAppBar title={deck.deckName}>
</MyAppBar>
<MuiCard key={card?.id || 0} sx={{
paddingLeft: { xs: 2, sm: 5 }, paddingRight: { xs: 2, sm: 5 },
minWidth: 275, minHeight: "40vh",
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
}}>
<CardContent sx={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
flexFlow: 'column',
flexGrow: 1,
justifyContent: 'space-between',
}}>
<Typography variant="h4" component="div" sx={{ flex: '0 1 auto' }}>
{card?.name}
</Typography>
<Typography sx={{ mt: 10, px: { sm: 4, md: 10 }, flex: '1 1 auto' }} variant="h5" component="div">
{card ? card.description : 'There are no cards in this deck.'}
</Typography>
<Typography sx={{ marginTop: "auto", pt: 8, flex: '0 1 40px' }} color="text.secondary" >
{formatLastUsedDate(card?.dateUsed ?? '')}
</Typography>
</CardContent>
<CardActions sx={{ marginTop: 'auto' }}>
<Button disabled={card === null}
onClick={e => {
setJustClicked(false);
router.push(`/card/pick?deck=${deck.id}`);
}}>
pick a random card from this deck
</Button>
<Button disabled={justClicked || card === null} onClick={e => {
e.preventDefault();
setJustClicked(true);
markUsed(card?.id ?? 0);
}} >mark as used</Button>
</CardActions>
</MuiCard>
</Container>
)
}
export default CardPage
export const getServerSideProps: GetServerSideProps = async (context) => {
// two options: either a specific card id passed, or else a deck id,
// from which we should randomly pick
const dateBefore = dayjs().subtract(3, 'day').toDate(); // don't re-pick any items for three days
// TODO: add a property for "don't pick again" to avoid the last ID picked
let deckId = context.query.deck;
let cardId = context.query.id;
let deck: Deck | null = null;
let card: Card | null = null;
if (deckId !== undefined) {
const filter = {
where: {
idDeck: Number(deckId),
OR: [
{ dateUsed: null },
{ dateUsed: { lte: dateBefore } }
]
}
};
// pick a random card
const cardCount = await prisma.card.count({
...filter
});
// are there any cards within the date range?
if (cardCount === 0) {
// no: just pick *any* card from the deck
card = await prisma.card.findFirst({
where: {
idDeck: Number(deckId)
}
});
} else {
const skip = Math.floor(Math.random() * cardCount);
card = await prisma.card.findFirst({
skip: skip,
...filter
});
}
}
if (card === null && cardId !== '' && !Number.isNaN(Number(cardId))) {
card = await prisma.card.findFirst({
where: {
id: Number(cardId)
}
});
}
if (card !== null) { deckId = card?.idDeck.toString() ?? '0'; }
deck = await prisma.deck.findFirst({
where: {
id: Number(deckId)
}
}) || { id: 0, deckName: "None" } as Deck;
return {
props: {
card: JSON.parse(JSON.stringify(card)), // TODO: research this prisma Date hack; see https://stackoverflow.com/questions/72176573/object-object-object-cannot-be-serialized-as-json-please-only-return-js
deck
}
}
}
Your useEffect is missing a dependency array. Try this:
useEffect(() => {
router.push(`/card/${card?.id}`, undefined, { shallow: true })
}, []);

this.props.route.params returns value as undefined

I'm building a barcode reader app that scans that qr code and then takes data and is used as a key to fetch an object from firebase. In order the data to be used as a key I need to pass through another screen but when I check console log it's cameback that the scanned key is undefined.
The itself barcode scanner works perfectly.
Barcode class :
export class BarCodeScannerScreen extends Component{
state = {
CameraPermissionGranted: null,
}
async componentDidMount() {
// Ask for camera permission
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ CameraPermissionGranted: status === "granted" ? true : false });
};
barCodeScanned = ({ data }) => {
//Access the Data
alert(data); // shows the scanned key
this.props.navigation.navigate('Info', {
item: data, }); // but then it's dissapears in here.
};
render(){
const { CameraPermissionGranted } = this.state;
if(CameraPermissionGranted === null){
// Request Permission
return(
<View style={styles.container}>
<Text>Please grant Camera permission</Text>
</View>
);
}
if(CameraPermissionGranted === false){
// Permission denied
return (
<View style={styles.container}>
<Text>Camera Permission Denied.</Text>
</View>
);
}
if(CameraPermissionGranted === true){
// Got the permission, time to scan
return (
<View style = {{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<BarCodeScanner
onBarCodeScanned = {this.barCodeScanned }
style = {{
height: DEVICE_HEIGHT/1.1,
width: DEVICE_WIDTH,
}}
>
</BarCodeScanner>
</View>
);
}
}
}
Here is my Info screen that receives the information :
export default class InfoScreen extends Component {
constructor(props){
super(props);
this.state={
productlist:[],
scannedkey: this.props.route.params.item
} }
async componentDidMount(){
firebase.database().ref(`product/${ this.state.scannedkey}`).on(
"value",
(snapshot) => {
var list = [];
snapshot.forEach((child) => {
list.push({
key: child.key,
title: child.val().title,
//details: child.val().details,
//price: child.val().price
});
});
this.setState({ productlist: list });
},
(error) => console.error(error)
);
}
componentWillUnmount() {
if (this.valuelistener_) {
this.valueRef_.off("value", this.valuelistener_)
}}
render() {
console.log(this.state.scannedkey); // console log shows that scanned key is undefined
return(
<View style={styles.container}>
<Text>Hey</Text>
<Text>{this.state.productlist.title}</Text>
</View>
);}}
App.js
export default function App() {
const Drawer=createDrawerNavigator();
return (
<Provider store={store}>
<NavigationContainer>
<Drawer.Navigator initialRouteName="Barcode">
<Drawer.Screen name="Barcode" component={BarCodeScannerScreen} />
<Drawer.Screen name="Info" component={InfoScreen} />
</Drawer.Navigator>
</NavigationContainer>
</Provider>
);
}
I ussualy use function components to navigate through but with class components it's a little tricky for me. Perhaps I missed something?
So far I 've tried :
this.props.navigation.navigate('Info', {
item: JSON.stringify(data) , });
And it didn't work.
I will be grateful for your help.
Try to use item directly from props, not from state
in your componentDidMount call where you supply from state the scannedKey, supply it from props
firebase.database().ref(`product/${this.props.route.params.item}`)....
you are also calling this.props instead of props directly in your state inside your constructor, which have direct access to it, that's why you can call super(props) and not super(this.props), I am not sure if this is the issue, but in react docs says don't copy props to state because they get ignored, and it's bad practice my friend.
check this link, in the big yellow note what I am reffering to
https://reactjs.org/docs/react-component.html#constructor

getting lat/long coordinates from firebase/firestore collection

im relative new to react native and firebase and it would be awesome if anyone could help me with this problem. currently when im adding new posts to my firebase collection i display all post with a flatlist and it works fine. but is it possible to get only the currentLatitude and currentLongitude for my markers? my target is to generate a new marker for each post.
Events = []
this.firestore.collection("Events").get().then(snapshot => {
snapshot.forEach(doc => {
Events.push(doc.data())
})
})
render() {
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={{ flex: 1 }}>
<MapView
provider={PROVIDER_GOOGLE}
mapType='hybrid'
showsUserLocation style={{flex: 1}}>
<MapView.Marker
coordinate={{latitude: //currentLatitude,
longitude: //currntLongitude}}
title={("Test")}
description={("Test")}
/>
</MapView>
</View>
</SafeAreaView>
);
}
}
#DevAS thanks for your patience.. this was made from 3 different .js files.. but I don't now how to get it just into the map.js.
The final result should look something like this:
enter image description here
Everything except for the lat/lng cords are supposed to be in the callout-window.
Item.js:
import { Ionicons } from '#expo/vector-icons';
import React from 'react';
import { Image, StyleSheet, Text, View } from 'react-native';
import Fire from '../screens/Fire'
const profileImageSize = 36;
const padding = 12;
export default class Item extends React.Component {
state = {
user: {}
};
componentDidMount() {
const user = this.props.uid || Fire.shared.uid;
this.unsubscribe = Fire.shared.firestore
.collection("users")
.doc(user)
.onSnapshot(doc => {
this.setState({ user: doc.data() });
});
if (!this.props.imageWidth) {
// Get the size of the web image
Image.getSize(this.props.image, (width, height) => {
this.setState({ width, height });
});
}
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { title, address, name, imageWidth, imageHeight, image, currentLatitude, currentLongitude } = this.props;
// Reduce the name to something
const imgW = imageWidth || this.state.width;
const imgH = imageHeight || this.state.height;
const aspect = imgW / imgH || 1;
return (
<View>
<Header image={{ uri: this.state.user.avatar }} name={this.state.user.name} />
<Image
resizeMode="contain"
style={{
backgroundColor: "#D8D8D8",
width: "100%",
aspectRatio: aspect
}}
source={{ uri: image }}
/>
<Metadata
name={this.state.user.name}
address={address}
title={title}
currentLongitude={currentLongitude}
currentLatitude={currentLatitude}
/>
</View>
);
}
}
const Metadata = ({ name, address, title, currentLongitude, currentLatitude}) => (
<View style={styles.padding}>
<IconBar />
<Text style={styles.text}>{name}</Text>
<Text style={styles.subtitle}>{address}</Text>
<Text style={styles.subtitle}>{title}</Text>
<Text style={styles.subtitle}>Lat: {currentLatitude}</Text>
<Text style={styles.subtitle}>Lng: {currentLongitude}</Text>
</View>
);
const Header = ({ name, image }) => (
<View style={[styles.row, styles.padding]}>
<View style={styles.row}>
<Image style={styles.avatar} source={image} />
<Text style={styles.text}>{name}</Text>
</View>
<Icon name="ios-more" />
</View>
);
const Icon = ({ name }) => (
<Ionicons style={{ marginRight: 8 }} name={name} size={26} color="black" />
);
const IconBar = () => (
<View style={styles.row}>
<View style={styles.row}>
<Icon name="ios-heart-empty" />
<Icon name="ios-chatbubbles" />
<Icon name="ios-send"/>
</View>
<Icon name="ios-bookmark" />
</View>
);
const styles = StyleSheet.create({
text: { fontWeight: "600" },
subtitle: {
opacity: 0.8
},
row: {
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center"
},
padding: {
padding
},
avatar: {
aspectRatio: 1,
backgroundColor: "#D8D8D8",
borderWidth: StyleSheet.hairlineWidth,
borderColor: "#979797",
borderRadius: profileImageSize / 2,
width: profileImageSize,
height: profileImageSize,
resizeMode: "cover",
marginRight: padding
}
});
List.js
import React from 'react';
import { FlatList } from 'react-native';
import Footer from './Footer';
import Item from './Item';
class List extends React.Component {
renderItem = ({ item }) => <Item {...item} />;
keyExtractor = item => item.key;
render() {
const { onPressFooter, ...props } = this.props;
return (
<FlatList
keyExtractor={this.keyExtractor}
ListFooterComponent={footerProps => (
<Footer {...footerProps} onPress={onPressFooter} />
)}
renderItem={this.renderItem}
{...props}
/>
);
}
}
export default List;
FeedScreen.js
import firebase from "firebase";
import React, { Component } from "react";
import { LayoutAnimation, RefreshControl } from "react-native";
import List from "../components/List";
import Fire from "./Fire";
// Set the default number of images to load for each pagination.
const PAGE_SIZE = 5;
console.disableYellowBox = true;
export default class FeedScreen extends Component {
state = {
loading: false,
data: {}
};
componentDidMount() {
// Check if we are signed in...
if (Fire.shared.uid) {
// If we are, then we can get the first 5 posts
this.makeRemoteRequest();
} else {
// If we aren't then we should just start observing changes. This will be called when the user signs in
firebase.auth().onAuthStateChanged(user => {
if (user) {
this.makeRemoteRequest();
}
});
}
}
// Append the item to our states `data` prop
addPosts = posts => {
this.setState(previousState => {
let data = {
...previousState.data,
...posts
};
return {
data,
// Sort the data by timestamp
posts: Object.values(data).sort((a, b) => a.timestamp < b.timestamp)
};
});
};
// Call our database and ask for a subset of the user posts
makeRemoteRequest = async lastKey => {
// If we are currently getting posts, then bail out..
if (this.state.loading) {
return;
}
this.setState({ loading: true });
// The data prop will be an array of posts, the cursor will be used for pagination.
const { data, cursor } = await Fire.shared.getPaged({
size: PAGE_SIZE,
start: lastKey
});
this.lastKnownKey = cursor;
// Iteratively add posts
let posts = {};
for (let child of data) {
posts[child.key] = child;
}
this.addPosts(posts);
// Finish loading, this will stop the refreshing animation.
this.setState({ loading: false });
};
// Because we want to get the most recent items, don't pass the cursor back.
// This will make the data base pull the most recent items.
_onRefresh = () => this.makeRemoteRequest();
// If we press the "Load More..." footer then get the next page of posts
onPressFooter = () => this.makeRemoteRequest(this.lastKnownKey);
render() {
// Let's make everything purrty by calling this method which animates layout changes.
LayoutAnimation.easeInEaseOut();
return (
<List
refreshControl={
<RefreshControl
refreshing={this.state.loading}
onRefresh={this._onRefresh}
/>
}
onPressFooter={this.onPressFooter}
data={this.state.posts}
/>
);
}
}

How to fetch Firebase data, store it Into an array and then display it using flatlist?

I am fetching data from firebase and storing them into array. Now I want to display them using flatlist, but I don't know how?
class HomeScreen extends Component{
constructor(props){
super(props);
this.state={
menu:[]
}
}
// componentDidMount() {
// firebase.auth().onAuthStateChanged(authenticate => {
// if (authenticate) {
// this.props.navigation.replace("Home");
// } else {
// this.props.navigation.replace("login");
// }
// });
// }
componentDidMount() {
firebase.database().ref('menu/starter/').once('value').then(snapshot => {
var items = [];
snapshot.forEach((child) => {
items.push({
name: child.val().name,
image: child.val().image,
price: child.val().price,
});
});
this.setState({ menu: items});
console.log(this.state.menu)
});
}
render(){
return(
<View style={styles.container}>
<Text></Text>
</View>
)
}
}
<FlatList
data={this.state.menu}
keyExtractor={elem => elem.name}
renderItem={elem => (<View><Text>{elem.item.name}</Text></View>)}
/>
renderItem is the callback to render each of your array item

How to view react native application database with tables

I'm creating a react native app. now I want to view my App database tables.
I don't know how many tables are in my SQLite database.
I am new in react native development & SQLite please help. to solve this issue
You can solve this problem through the Table View command. It can also be used to view data for that table.
/*Screen to view all the table*/
import React from 'react';
import { FlatList, Text, View } from 'react-native';
import { openDatabase } from 'react-native-sqlite-storage';
var db = openDatabase({ name: 'UserDatabase.db' });
export default class ViewAllTable extends React.Component {
constructor(props) {
super(props);
this.state = {
FlatListItems: [],
};
db.transaction(tx => {
tx.executeSql('SHOW TABLES', [], (tx, results) => {
var temp = [];
for (let i = 0; i < results.rows.length; ++i) {
temp.push(results.rows.item(i));
}
this.setState({
FlatListItems: temp,
});
});
});
}
ListViewItemSeparator = () => {
return (
<View style={{ height: 0.2, width: '100%', backgroundColor: '#808080' }} />
);
};
render() {
return (
<View>
<FlatList
data={this.state.FlatListItems}
ItemSeparatorComponent={this.ListViewItemSeparator}
keyExtractor={(item, index) => index.toString()}
renderItem={({item, index }) => (
<View key={item[index]} style={{ backgroundColor: 'white', padding: 20 }}>
<Text>Table: {item[index]}</Text>
</View>
)}
/>
</View>
);
}
}

Resources