SOLVED: How much do non-skippable in-stream YouTube ads cost on a CPM basis?

Struggling to find some basic estimates on how much your non-skippable in-stream YouTube ads are going to cost on a CPM basis? Finding recent CPM figures can be really tough! This morning I was trying to figure out how much it might cost to run some non-skippable ads on YouTube to promote my new budgeting app, BitBudget, and I was having the hardest time finding any actual figures. Well without further ado, here is the figure I found: $13

If you want to run your ads in the United States targeting 25 – 44 year olds $13 is what Google suggests it’s going to take to submit a competitive bid. How do I know this? Well I was fumbling around in the Google Ads manager and I noticed while trying to create a fake test campaign that a little yellow warning will display inside the manager if you enter too low of a CPM bid. I started at $10, and that’s when I first noticed the little yellow warning. However, after incrementing my bid one dollar at a time the warning dialog disappeared when I got to $13. So there you have it!

If you’re looking to set a competitive CPM bid for the pricier non-skippable variety of YouTube ads, $13 is a good starting point.

Too Low

Just Right

 

React Native Hyperlink

This blog post is brought to you by the developer of BitBudgetBitBudget is an automated budgeting app for Android and which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

Need to insert a hyperlink into your React Native application? This is actually super simple! Just use React Native’s built in ‘Linking’ module and open URLs with ease:

 

BitBudget Customer Support

Need help with anything related to the BitBudget mobile app for iOS or Android? Contact me!

Founder, Developer, and Customer Support Rep: Christopher Pedersen

Phone Number: +1 (469) 261-0727

Email: chris@topherpedersen.com

 

My Super Sweet MySQL Quickstart Guide (using Ubuntu Linux on DigitalOcean)

This blog post is brought to you by the developer of BitBudget. BitBudget is an automated budgeting app for Android and iOS which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

Hello internet friend. Welcome to my blog. This post actually isn’t for you, it’s for me! A lot of times I like to use this site to bookmark things I want to remember, so that’s sort of the purpose of this post. However, maybe you might find this helpful as a reference. So without further ado, I present:

My Super Sweet MySQL Quickstart Guide

Step 1: Spin up a MySQL server droplet

Make sure to use the MySQL droplet image available on the DigitalOcean marketplace. You could also use DigitalOcean’s managed database product, but it’s more expensive. Please note that this guide does not cover how to get up and running with the managed database product as the steps are different.

Step 2: Log in to your server from the command line using the password you created when spinning up your MySQL droplet

$ ssh root@123.45.67.891

Step 3: Look up your MySQL root password

$ nano /root/.digitalocean_password

You should find a file that contains your root MySQL password, your admin MySQL password, and your app MySQL password. The file will look something like this:

 root_mysql_pass="234g234jhg23jh4hg34hg3h4g3hg4h3" admin_mysql_pass="5k56kj56k5g4g4jh54j3jhdkk3jj3j" app_mysql_pass="sdytsydtfshdffhsdhh2342323423423" 

NOTE: You won’t actually be using any of the three passwords above, as we are going to create a new database user below in the next step. However, it’s still nice to have.

Step 4: Create a new MySQL user from the command line

$ sudo mysql -u root

CREATE USER 'dba'@'localhost' IDENTIFIED BY 'P@$$w0rD!';

CREATE USER 'dba'@'%' IDENTIFIED BY 'P@$$w0rD!';

GRANT ALL PRIVILEGES ON *.* TO 'dba'@'localhost';

GRANT ALL PRIVILEGES ON *.* TO 'dba'@'%';

Step 5: Create a new database connection in MySQL Workbench (or DBeaver) so you can connect to your database using a desktop application with a graphical user interface (gui)

If you are using MySQL Workbench to administer your database, this screenshot demonstrates what information you need to enter to log in to your newly created MySQL database using the MySQL Workbench GUI software. Remember to click the ‘save in keychain’ button to enter your passwords. Use the ‘Test Connection’ button to test the database connection, and once you get a good test connection, press ‘OK’ to log in:

If you’re using DBeaver:

  • Click “Database”
  • Click “New Database Connection”
  • Select MySQL

Then fill in the fields with the appropriate information.

On the ‘General’ Tab, enter:

  • Server Host: localhost
  • Port: 3306
  • Database: LEAVE BLANK
  • Username: dba
  • Password: ENTER dba PASSWORD CREATED IN STEP 4

On the ‘SSH’ Tab:

  • Check the ‘Use SSH Tunnel’ box
  • Host/IP: ENTER IP ADDRESS FOR MYSQL DROPLET
  • Port: 22
  • Username: root
  • Password: ENTER YOUR NEW SERVER DROPLET PASSWORD THAT YOU CREATED WHEN SPINNING UP YOUR MYSQL SERVER DROPLET

Click ‘Test Connection’

Click ‘Finish’ to Log In

Right click ‘MYSQL LOCALHOST’ and then click ‘RENAME’ to give your MySQL database a more descriptive name in the DBeaver GUI application

 

Dumping Apache2 and mod_wsgi for Gunicorn & NGINX: The Missing Guide for Getting Up & Running with Python + Flask in Production

This blog post is brought to you by the developer of BitBudget. BitBudget is an automated budgeting app for Android and iOS which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

Well hello there internet friend. Having trouble getting your Flask app up and running in production? This can be quite the pain! In the past I’ve used Apache2 and mod_wsgi to serve my Flask apps, but getting it all setup tends to be a serious headache. It doesn’t help that the official docs on the Flask website are pretty thin, and don’t really list all of the steps you need to get everything working. Furthermore, a lot of the blog posts on the topic of Apache2, mod_wsgi, and Flask don’t always work either.

And that’s what brings me here to WordPress tonight to write this blog post. I’m currently working on pushing a new project to production, and well… I can’t seem to get Apache to play nice with my Flask app! (even though I’ve already done this before in the past). And I’m not sure exactly what’s causing my issues. Is it the switch from Ubuntu 16 to Ubuntu 18? The newest version of Apache2 doesn’t work with the old configuration file? I just don’t know. All I do know is: there has to be a better way!

So after several hours of fumbling around with Apache, I decided that most of the people in the Flask community probably aren’t using Apache, so maybe a good first step would be to try and pick a more common setup: Gunicorn + NGINX.

If you happen to have stumbled upon this blog post trying to get Apache and mod_wsgi to work with Flask, I suggest you simply ditch Apache and use Gunicorn + NGINX instead. Specifically, I recommend you follow along with this excellent tutorial which I just discovered from DigitalOcean: How To Serve Flask Applications with Gunicorn and Nginx on Ubuntu 18.04

In fact, the real purpose of this blog post is that I want to bookmark that tutorial so I don’t lose it! The guide is from the people at DigitalOcean, but the steps described should work with any Linux Virtual Server. But before you get started following that guide, there are a few prerequisites steps:

  1. Setup a non-root user with sudo privileges on your server
  2. Install NGINX
  3. Buy a domain name and point it at your server

But before following along with the DigitalOcean guide above, please note that there may be an error in the guide related to the myproject.service file that you’ll be instructed to create. For me, gunicorn doesn’t seem to install into my python virtual environment so I had to install it globally:

$ sudo apt install gunicorn

And then change ExecStart line in myproject.service to:

ExecStart=gunicorn --workers 3 --bind unix:myproject.sock -m 007 wsgi:app

And one last note, towards the end of the guide above you are going to need to specify how many worker processes you want serving up your Flask app. I had absolutely no idea how many to specify as this was not something I had to worry about in the past when setting up Apache and Flask. According to gunicorn’s documentation a rule of thumb is (2 * numberOfServerCores) + 1. So if that part of the guide sticks out when you get to it, go ahead and use that rule of thumb for estimating how many worker processes you want to provision. And that’s it!

I know it’s a lot of steps to get all of this up and running. But I do think that the guide I’ve bookmarked here in this post is the best I’ve found. However, if you don’t have any experience with Linux and some of this stuff is over your head, there are other options. But if you’re like me and you do have a fair amount of experience with that kind of stuff, but are just stuck getting Apache configured, ditch Apache/mod_wsgi and go with Gunicorn/NGINX.

UPDATE (June 1st, 2020): One thing I forgot to mention is how to get Gunicorn and NGINX to reload changes that you make to your source code. If you push a new version of your app to the server, you can use the following command(s) to make sure the changes are served:

Restart Gunicorn

$ sudo systemctl restart myproject

Restart NGINX (if necessary)

$ sudo systemctl restart nginx

Please note that the name “myproject” is what the authors of the tutorial mentioned above called their project in the example, so for consistency I’ve used the same name. Replace ‘myproject’ with whatever project name you chose while following along with the DigitalOcean tutorial.

UPDATE (More Notes to Self): If you need to install dependencies on the production server and are using a virtual environment as described in the tutorial, log into the server using SSH as a non-root user, navigate to your project directory, activate the virtual environment, and then install your dependencies:

$ cd ~/myproject

$ source myprojectenv/bin/activate

(myprojectenv) $ pip install supersweetlib.py

Or if you have a requirements.txt file…

(myprojectenv) $ pip install -r requirements.txt

UPDATE (April 17th, 2022): The last time I tried deploying a production flask app using the steps described in this blog post along with the DigitalOcean guide, I kept running into a bunch of problems with gunicorn. It looks like my issue was the gunicorn was not actually getting installed into my virtual environment so the ExecStart command in myprojectname.service was never able to actually start up gunicorn. To fix this issue, when you install gunicorn in your virtual environment make sure to use the -I (that’s a capital i, not a lowercase L) or –ignore-installed flag to ensure that gunicorn actually gets installed into your virtual environment:

(venv) $ pip install -I gunicorn

 

How to autoFocus a TextInput field within a Modal in React-Native

This blog post is brought to you by the developer of BitBudget. BitBudget is an automated budgeting app for Android and iOS which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

Using the autoFocus prop with TextInputs in React-Native is normally straightforward. However, I’ve noticed that attempting to autoFocus TextInput fields contained within the simple <Modal/> component that comes with React-Native, or the 3rd party react-native-modal doesn’t seem to work. I think the problem likely has to do with how the <Modal/> component is designed. You typically embed the <Modal/> on a screen and just toggle the value of a prop whenever you want to show or hide it. And for whatever reason, this doesn’t seem to play well with autoFocus.

But fret not! Over on Stack Overflow I discovered an excellent work-around from Saleel. Saleel suggests that instead of using autoFocus, give your TextInput a ref, and then use that ref from within the Modal’s onShow method to call the TextInput’s .focus() method:

<Modal
visible={true}
onShow={ () => { this.textInput.focus(); }}>
{/* Adapted From: https://stackoverflow.com/questions/42730400/focus-input-on-load-of-modal-in-react-native */}
<KeyboardAvoidingView style={{}}>
<TextInput
style={{}}
ref={ (input) => { this.textInput = input; }}
placeholder=" Enter Text Here"
onChangeText={ (text) => console.log(text) }
value={ null } />
<Button
title="Submit"
onPress={ () => console.log("do stuff...") } />
</KeyboardAvoidingView>
</Modal>
 

How to skip, filter, and remove items and rows when rendering a FlatList in React Native

This blog post is brought to you by the developer of BitBudget. BitBudget is an automated budgeting app for Android and iOS which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

Having trouble working with FlatList’s in React Native? Something I have been struggling to figure out recently is how to go about excluding certain items and rows when rendering a FlatList in React Native. For example, let’s say you have an array of items stored in either Redux or in React’s built-in component state and you want to remove the item? Well this is fairly straight forward, you just remove the item from the store and the FlatList will update accordingly. However, what if you want to keep the item in the store but you want to filter it out from your FlatList?

Unfortunately the React Native docs don’t mention how to do this exactly. Furthermore, I wasn’t able to find any blog posts or Stack Overflow answers regarding this either. However, I was able to stumble upon the answer today while experimenting, so here is the answer:

If you wish to skip, filter, remove, or omit an item from your FlatList, add some conditional logic to your <FlatListItem /> component’s render method so that it returns nothing <></> when passed a particular prop. For example, take a look at my GitHub gist below. In the example I have a list of three items stored in my my <App/>’s component state. The three items are called “foo”, “bar”, and “baz”. Now, in the <FlatListItem />’s render method I have added some logic which states if the item is equal to “bar”, render nothing <></>.

import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
StatusBar,
FlatList,
} from 'react-native';
// SEE BLOG POST FOR EXPLANATION REGARDING HOW TO SKIP
// OVER CERTAIN ITEMS AND ROWS IN A REACT NATIVE FLAT LIST
// https://topherpedersen.blog/2020/05/16/how-to-skip-filter-and-remove-items-and-rows-when-rendering-a-flatlist-in-react-native/
class FlatListItem extends React.Component {
constructor(props) {
super(props);
}
render() {
if (this.props.text !== "bar") {
return(
<View>
<Text>{this.props.text}</Text>
</View>
);
} else {
return(
<></>
);
}
}
}
const Empty = <></>;
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [
{text: "foo"},
{text: "bar"},
{text: "baz"},
],
}
}
render() {
return(
<SafeAreaView style={{flex: 1}}>
<View style={{flex: 15}}>
<Text style={{textAlign: 'center'}}>FlatListApp</Text>
</View>
<View style={{flex: 85, justifyContent: 'center', backgroundColor: 'white'}}>
<FlatList
key="flat-list-key"
data={this.state.items}
renderItem={({ item }) => (
<FlatListItem text={item.text} />
)}
keyExtractor={ (item, index) => index.toString() }/>
</View>
</SafeAreaView>
);
}
}
export default App;

So that’s all there is to it! Break the items you wish to render off into their own class components, and then add some logic to render nothing <></> under some particular condition, and React Native will simply skip over that item when rendering the FlatList.

 

Who is Delane Parnell? and how did he convince investors to give him $96,000,000?

Yesterday, the This Week in Startups podcast featured an interesting guest with a fantastic tale: Delane Parnell, CEO and founder of PlayVS— the “Official High School eSports League.” The thing that stuck out for me watching the episode was the guest’s seemingly unbelievable story. Parnell paints an origin story of himself as a sort of business prodigy who from a young age was always destined for great success. He claims to have kicked off his entrepreneurial career by opening his own cell phone store in Detroit at age 16 while still in High School, before moving on to start his own car rental business around the same time.

After selling his stores and car rental business, Parnell became a figure in the Detroit startup scene by launching his own startup event and speaker series modeled after Jason Calacanis’s Launch Festival. Leveraging his newly found status in the Detroit entrepreneur scene, Parnell landed a job at a small seed stage Venture Capital firm becoming the youngest black Venture Capitalist in America.

All of this leads to Parnell meeting LA based startup founder and venture capitalist Peter Pham at SXSW in 2017 who convinced him to “move to LA to become a billionaire.” Enticed by Pham, Parnell moves to Los Angeles and goes on to raise nearly $100,000,000 for his newly founded startup, PlayVS, in just 13 months!

But it makes you wonder: How did a young man with little experience in eSports, no computer programming skills, and no experience creating tech startups, convince investors to give him $96,000,000? Had Parnell raised $96,000 the story above might make some sense. But $96,000,000?

From my research PlayVS’s footprint and web presence does not match the amount of funding they have raised. They have a very small following on Twitch, around 1,000 followers. And after registering as a player (and coach) on their website, I discovered that their website is empty and unfinished. From what I could tell, they are currently using a lean startup style website that appears at first glance to be fully functioning, but is really just a tool for collecting email addresses. According to Parnell there is currently a wait-list to join, so that maybe explains a few things. But from my research it certainly looks a lot like vaporware to me. For example, here’s what the website looks like if you signup as a player:

The site simply notifies the player that his school needs a coach. Sounds reasonable right? But after registering a coach account with a different email address, the website is empty for coaches as well:

There’s nothing there! You can click on the user icon to update the coaches personal information, but that’s it! So when Parnell says that there is currently a “waitlist,” it’s quite possible that PlayVS hasn’t actually built anything yet and is using the term “waitlist” as a convenient euphemism.

I don’t know. I may be wrong, but what do you think? Could Delane Parnell be the next Evan Spiegal? Or is he the next Billy McFarland? Only time will tell, so we’ll have to see how this post ages. But if PlayVS does happen to fail spectacularly, you heard it here first!

Note, at the time of this blog post https://topherpedersen.blog has a higher Alexa Internet Ranking in the United States than https://playvs.com. Red flag?

 

Simple String Similarity Algorithm in JavaScript or, How to Tell if Two Strings are Similar, even if they aren’t exactly the same

This blog post is brought to you by the developer of BitBudget. BitBudget is an automated budgeting app for Android and iOS which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

At the moment I happen to be working on a personal finance app called BitBudget, where I need to be able to figure out if two purchases were made with the same merchant, or different merchants. For example, does a financial transaction labeled “VZWRLSS*PRPAY AUTOPAY 02/19 PURCHASE” represent a transaction with the same company as “VZWRLSS*PRPAY AUTOPAY 03/19 PURCHASE”? What about “CALM 03/26 PURCHASE” and “CALM 02/26 PURCHASE INTERNET”? Many transactions are easy to compare, “McDonald’s” vs “Amazon”, or “Chipotle Mexican Grill” vs “Starbucks”, but once banks and payment processors start throwing in all this extra information things get tricky.

While I personally am not really a computer science / algorithms kind of guy, sometimes you really need an algorithm to get the job done! So if you find yourself in the same boat I have your answer right here. After a quick google search on this topic I discovered that there appear to be three different types of string comparison algorithms:

  1. Edit Distance Based
  2. Token Based
  3. Sequence Based

If you’d like to read more about the three types of algorithm’s I suggest reading: String similarity — the basic know your algorithms guide!

For my use-case, I decided that an Edit Distance Based algorithm would be the quickest strategy to implement myself, so that’s what I’m going with and would like to present below. The actual code that I will end up using in my app may feature a few modifications for speed, such as shortening the number of iterations before returning a result, but what I have so far should be a good start if you need something quick and easy:

var string = [];
string[0] = 'CALM 03/26 PURCHASE';
string[1] = 'CANVA* 02644-3020257 03/28 PURCHASE HTTPSCANVA.CO DE';
string[2] = 'VZWRLSS*PRPAY AUTOPAY 03/19 PURCHASE';
string[3] = 'WWW.NORDVPN.COM HTTPSWWW.NORD';
var findMe = 'CALM 02/26 PURCHASE INTERNET';
function calculateSimilarity(aString, bString) {
var matchingCharacters = 0;
var totalCharacters;
if (aString.length >= bString.length) {
totalCharacters = aString.length;
} else {
totalCharacters = bString.length
}
var shortestStringLength;
if (aString.length < bString.length) {
shortestStringLength = aString.length;
} else if (bString.length < aString.length) {
shortestStringLength = bString.length;
} else {
shortestStringLength = aString.length;
}
for (var i = 0; i < shortestStringLength; i++) {
if (aString[i] === bString[i]) {
matchingCharacters++;
}
}
var percentSimilar = matchingCharacters / totalCharacters;
return percentSimilar;
}
console.log(calculateSimilarity(findMe, string[0]).toString());
console.log(calculateSimilarity(findMe, string[1]).toString());
console.log(calculateSimilarity(findMe, string[2]).toString());
console.log(calculateSimilarity(findMe, string[3]).toString());
 

Working with ISO 8601 dates in Python Part II

This blog post is brought to you by the developer of BitBudget. BitBudget is an automated budgeting app for Android and iOS which syncs with your bank account and helps you avoid overspending. If you’d like to quit living paycheck-to-paycheck and get a better handle on your finances, download it today! https://bitbudget.io

If you happen to have stumbled upon this post I apologize, I’m not going to be doing a lot of explaining here. Rather, I really just wanted to post this little snippet of code for my own reference so I don’t lose it! But hey, maybe you’ll find it useful too…

from datetime import date, datetime
now = datetime.now()
print(now)
now_iso_8601 = now.isoformat()
print(now_iso_8601)
import time as time_
current_time = time_.time()
print(current_time)
# date.isoformat() vs date.fromisoformat
date_from_iso_format = date.fromisoformat('1987-02-06')
print(date_from_iso_format)
date_isoformat = date.isoformat(now)
print(date_isoformat)
# REFERENCE: https://topherpedersen.blog/2019/07/12/how-to-get-the-current-year-month-and-day-as-integers-in-python-3-using-the-datetime-module/
# REFERENCE: https://www.w3schools.com/python/python_datetime.asp
year = now.year
month = now.month
day = now.day
print("One month ago...")
one_month_ago = date(year, month - 1, day)
print(one_month_ago)
print("One year ago...")
one_year_ago = date(year - 1, month, day)
print(one_year_ago)
print("Two years ago...")
two_years_ago = date(year - 2, month, day)
print(two_years_ago)