tag:blogger.com,1999:blog-57121697420617177562024-03-13T05:16:12.413-07:00Maplekey Companysmart, friendly softwareSteve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.comBlogger12125tag:blogger.com,1999:blog-5712169742061717756.post-71470582821302499442012-03-22T21:27:00.001-07:002012-03-22T21:27:14.016-07:00Cowboy + ErlyDTLThis is a continuation of <a href="http://maplekeycompany.blogspot.com/2012/03/very-basic-cowboy-setup.html">part 1</a> so I'm going to assume we're starting where we left off there.<br />
<br />
Now that we have a Cowboy server that can handle static pages, we need to set it up to handle dynamic pages. In comes <a href="https://github.com/evanmiller/erlydtl">ErlyDTL</a>. This is basically going to be the same as <a href="http://buffered.io/posts/webmachine-erlydtl-and-riak-part-1/">OJ's tutorial</a> but for Cowboy instead of webmachine.<br />
<br />
<b>ErlyDTL Install</b><br />
Change your <span style="font-family: "Courier New",Courier,monospace;">rebar.config</span> to include the ErlyDTL dependency.<br />
<script src="https://gist.github.com/2142154.js?file=rebar.config">
</script>
Run <span style="font-family: "Courier New",Courier,monospace;">./rebar get-deps compile</span><span style="font-family: inherit;"> to make sure that step worked.</span><br />
<br />
<b>Make a page controlled by code</b><br />
Modify <span style="font-family: "Courier New",Courier,monospace;">src/simple_server_http.erl</span> to add a path and handler name to the dynamic page you're creating in <span style="font-family: "Courier New",Courier,monospace;">dispatch_rules/0</span>. In this case, requests for the 'pony' resource (http://127.0.0.1/pony) should be handled by <span style="font-family: "Courier New",Courier,monospace;">simple_server_http_pony</span>.<br />
<script src="https://gist.github.com/2142154.js?file=simple_server_http.erl">
</script>
Create <span style="font-family: "Courier New",Courier,monospace;">src/simple_server_http_pony.erl</span>
<script src="https://gist.github.com/2142154.js?file=simple_server_http_pony.erl">
</script><br />
Create <span style="font-family: "Courier New",Courier,monospace;">templates/mylittlepony.dtl</span><br />
<script src="https://gist.github.com/2142154.js?file=mylittlepony.dtl">
</script>
<br />
Remove the previous release, compile, generate, run.<br />
<script src="https://gist.github.com/2142154.js?file=rebuild_restart.sh">
</script>
Go to <a href="http://127.0.0.1/pony">http://127.0.0.1/pony</a>. Dazzleglow is everyone's favorite pony.<br />
<br />
<b>Take in user input</b><br />
If only the world were so simple that we could just create answers... The following shows how to deal with GET/POST parameters so we can take input from those pesky users.<br />
<br />
Create a new dispatch rule and handler for a page called <a href="http://www.robert-louis-stevenson.org/novels/7-treasure-island">captainflint</a>. I'm going to assume you know how to add a dispatch rule to <span style="font-family: "Courier New",Courier,monospace;">src/simpleserver_http.erl</span> at this point.<br />
<br />
<script src="https://gist.github.com/2142154.js?file=captainflint.dtl">
</script>
<script src="https://gist.github.com/2142154.js?file=simple_server_http_captainflint.erl">
</script><br />
Go to <a href="http://127.0.0.1/captainflint?get_input=Arrr">http://127.0.0.1/captainflint?get_input=Arrr</a>. Thar she blows. Now you have a working example of how to access GET and multi-valued POST parameters.Steve Songhttp://www.blogger.com/profile/13780874289480663109noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-56899304183425805392012-03-19T13:21:00.001-07:002012-03-23T11:35:02.665-07:00A very basic Cowboy setupI'm new to using Erang in a "real" way, but I've been wanting to use it for a while. Looking around at the Erlang landscape these days, I don't feel bad for having waited four+ years. Some projects have come onto the scene in the last couple of years that make development in Erlang a practical reality for those of us who don't have the time to develop everything from first principles. Basho's webmachine was very close to being my web server of choice, but I needed web sockets so <a href="https://github.com/extend/cowboy">Cowboy</a> seemed like a better choice than cobbling together my own webmachine/web socket router.<br />
<br />
For today's deposit into the Google karma bucket, I'm going to run through the setup for a super duper simple Cowboy web server so you can move past the plumbing stage faster. I started from the <a href="http://www.metabrew.com/article/bigwig-erlang-webtool-spawnfest">BigWig</a> project and stripped stuff away until I had a web server that would serve static pages and 404s.<br />
<br />
<span style="font-size: small;"><span style="font-size: large;"><b>Pre-requisites</b></span> <span style="font-size: xx-small;">(Blogger, why do I have to bold this instead of just selecing an <H2>?)</span></span><br />
<a href="http://jaked409.livejournal.com/92737.html">Erlang</a> (R15B), <a href="http://www.metabrew.com/article/erlang-rebar-tutorial-generating-releases-upgrades">rebar</a>, Unix-y OS (Mac OSX in my case)<br />
<br />
<span style="font-size: large;"><b>Process</b></span><br />
<b>Setup</b><br />
Set up directories and copy over rebar binary
<br />
<script src="https://gist.github.com/2123991.js?file=simple_server1.sh">
</script>
<br />
Create <span style="font-family: "Courier New",Courier,monospace;">rebar.config</span> in your project directory<br />
<script src="https://gist.github.com/2123991.js?file=rebar.config">
</script>
<br />
Run rebar commands to create a basic server and release. It's easier to name everything the same as the project directory than it is to figure out what to change in the configs.<br />
<script src="https://gist.github.com/2123991.js?file=simple_server2.sh">
</script>
<br />
Modify <span style="font-family: "Courier New",Courier,monospace;">rel/reltool.config</span> so that <span style="font-family: "Courier New",Courier,monospace;">lib_dirs</span> points to the directory that contains the <span style="font-family: "Courier New",Courier,monospace;">src</span> directory - ["../.."]<br />
<script src="https://gist.github.com/2123991.js?file=reltool.config">
</script>
<br />
<b>Make a simple web server</b><br />
Add 5 new files and modify one of the generated files. I think there's a way to have rebar generate all of this, but I'm not going to bother figuring that out until I've got a better sense of what all I want to have generated for me on future projects.<br />
<br />
Create <span style="font-family: "Courier New",Courier,monospace;">src/simple_server.erl</span><br />
<script src="https://gist.github.com/2123991.js?file=simple_server.erl">
</script>
<br />
Create <span style="font-family: "Courier New",Courier,monospace;">src/simple_server_http.erl</span><br />
<script src="https://gist.github.com/2123991.js?file=simple_server_http.erl">
</script>
<br />
Create <span style="font-family: "Courier New",Courier,monospace;">src/simple_server_http_catchall.erl</span><br />
<script src="https://gist.github.com/2123991.js?file=simple_server_http_catchall.erl">
</script>
<br />
Create <span style="font-family: "Courier New",Courier,monospace;">src/simple_server_http_static.erl</span><br />
<script src="https://gist.github.com/2123991.js?file=simple_server_http_static.erl">
</script>
<br />
Create <span style="font-family: "Courier New",Courier,monospace;">priv/html/index.html</span><br />
<script src="https://gist.github.com/2123991.js?file=index.html">
</script>
<br />
Modify the <span style="font-family: "Courier New",Courier,monospace;">init/1<span style="font-family: inherit;"> function in </span>src/simple_server_sup.erl</span> to include your HTTP handler<b> </b><br />
<script src="https://gist.github.com/2123991.js?file=simple_server_sup.erl">
</script>
<br />
<b>Build and test</b><br />
The following will pull down Cowboy, compile all your files, and generate a release that you can run:<br />
<div style="font-family: "Courier New",Courier,monospace;">
./rebar get-deps compile generate</div>
<br />
From the rebar tutorials I've seen, you should be able to call:<br />
<span style="font-family: "Courier New",Courier,monospace;">./rel/simple_server/bin/simpler_server start</span> <br />
<br />
This call returns without error and if you look at your processes it looks like something's going on, but there'll be nothing for you in your web browser.<br />
<br />
Instead, call the following the start up your server:<br />
<div style="font-family: "Courier New",Courier,monospace;">
sudo erl -sname simple_server -pa $PWD/ebin $PWD/deps/*/ebin -boot start_sasl -s simple_server</div>
<br />
Make sure to call it with <span style="font-family: "Courier New",Courier,monospace;">sudo</span> since we have the port set to 80. If you set it to something like 8000 you don't have to call it with <span style="font-family: "Courier New",Courier,monospace;">sudo</span>.<br />
<br />
Open up <a href="http://127.0.0.1/">http://127.0.0.1</a> in your browser and celebrate. Open up <a href="http://127.0.0.1/blah">http://127.0.0.1/blah</a> in your browser and celebrate some more.<br />
<br />
<b>More</b><br />
Yay, now we have a lame server. Next up, we need to add templating and a datastore to do more interesting stuff. See <a href="http://maplekeycompany.blogspot.com/2012/03/cowboy-erlydtl.html">part 2</a>.Steve Songhttp://www.blogger.com/profile/13780874289480663109noreply@blogger.com3tag:blogger.com,1999:blog-5712169742061717756.post-32132334649194584812012-03-17T01:47:00.000-07:002012-03-17T01:47:45.079-07:00In-app purchases and children's appsAt the AppNation conference in SF last December, the conventional wisdom of the talks seemed to be that best business model for app developers is free plus in-app purchases. This makes a lot of sense for regular apps where the user is also the account owner. Free to get a lot of people to try your app, in-app purchases to present the buying opportunity when your users are in the buying mood. Brilliant. But what if the user isn't the account owner?<br />
<br />
This model breaks down for children's apps because the app proves its worth to an advocate and not to the buyer. Even the best kid-sized <strike>con-artists</strike> advocates have a hard time convincing the same parent to buy an add-on ten different times. It's better to package those ten things up into a single purchasing decision (think "pro" version) and ease the burden on your little salespeople.Steve Songhttp://www.blogger.com/profile/13780874289480663109noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-74785674473756096902012-03-16T22:27:00.000-07:002012-03-23T22:36:12.448-07:00Thoughts on the market for education/children's apps(Originally drafted this in October 2011. Writing about COPPA got me a little hot under the collar so I took a break to cool down and then things got busy. Figured I should finish this before posting new stuff.)<br />
<br />
Ran into a question on Quora that got me thinking. "<a href="http://www.quora.com/Technology-in-Education/Why-is-there-a-lack-of-innovation-in-educational-technology-for-school-aged-children-compared-to-high-school-higher-ed">Why is there a lack of innovation in educational technology for school-aged children (compared to high school & higher ed)?</a>"<br />
<br />
As an app developer, I'm going to interpret this as "Why are there cool apps for high school and higher, but not for elementary school?" The four reasons I see as the cause for this situation can be summarized as money, demographics, mix of skills, and legal.<br />
<br />
<b>Money</b><br />
Independent app developers have, in the words of Captain Malcolm Reynolds, "a powerful need to eat sometime this month". To this end, developers will gravitate towards app categories that make the most money or have the perception of making the most money. On the perception side, a search for 'app developer success stories' shows interviews with developers who made a pretty penny off of games, entertainment, games, utility, and games.<br />
<br />
On the real money-trail side, a look at some numbers from Distimo's <a href="http://www.distimo.com/publications">June 2011 report</a> and 148Apps.biz's <a href="http://148apps.biz/app-store-metrics/?mpage=catcount">App Store Metrics</a> from late September 2011 show that the money just isn't there.<br />
<br />
Distimo's numbers show a standard power law distribution with a couple of categories accounting for a vast majority of the downloads. Games and Entertainment account for about 65% of the free app downloads on iTunes and 75% of the paid app downloads in the US. Books and Education account for somewhere between a low single digits percentage share to 'too small of a line to be visible'.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://2.bp.blogspot.com/-MNHA_xXhAKU/TopPx_8vYnI/AAAAAAAAABE/jIMAG3JnYjk/s1600/Distimo-June-2011-app-distribution.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="176" src="http://2.bp.blogspot.com/-MNHA_xXhAKU/TopPx_8vYnI/AAAAAAAAABE/jIMAG3JnYjk/s400/Distimo-June-2011-app-distribution.png" width="400" /></a></div>
<br />
The disparity in download numbers could be attributed to a disparity in apps available to download. Maybe there are more games being downloaded simply because there are more games available. 148Apps.biz's numbers show that this isn't the case. Games represent 17% of the apps available while commanding 60% of the downloads. Books and Education represent 21% of the apps available while only garnering 2% of the downloads <i>combined</i>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="http://1.bp.blogspot.com/-1h7yatwlDc4/TopP1yWXgBI/AAAAAAAAABI/u0H1_fry9-U/s1600/148apps-app-category-distribution.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="http://1.bp.blogspot.com/-1h7yatwlDc4/TopP1yWXgBI/AAAAAAAAABI/u0H1_fry9-U/s400/148apps-app-category-distribution.png" width="332" /></a></div>
<br />
Money attracts attention and talent. The more people you have staring at a problem, the more likely someone will come up with something cool. That's not to say that those of us working in the education app space can't innovate. By the numbers, it'll just take us more time to saturate the ABC and periodic table sides of the spectrum and start filling in the middle.<br />
<br />
The number of developers might be proportional to the number of apps (and therefore there might be a lot of education developers even though there isn't as much money in it), but I suspect this is not the case. My guess is that many game development shops these days have multiple full-time people working to produce a game from a game designer, a sound designer, a graphic artist, and multiple developers. Most apps in the book and education categories are probably from much smaller one and two person operations. The description for one of the top flashcard apps even says that the developer only works on the app part-time as hobby. You don't have to be full-time to make a great implementation of a new idea, but the odds of creating something great are probably in favor of those who spend more time working on it.<br />
<br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">
<b>Demographics</b></div>
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">
There is a natural tendency to work on problems that you see. App developers are fairly young on average. Most probably don't have kids and those that do probably have very young children -- thus the plethora of ABC and 123 apps. Other developers can probably only think back as far as high school. It's hard to think of a time before you knew algebra and how you learned it.</div>
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">
<br /></div>
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">
</div>
<b>Mix of skills</b><br />
Producing a quality education app is hard. In addition to development skills, you need skills in graphic design, sound effects and composition, user experience design, and teaching. That's a lot of different skills to bring together. With games, you can play with them and pick apart what made things fun for you. With educational apps, there aren't as many examples to learn from and it's harder to analyze them because you already know what they are supposed to teach you.<br />
<br />
<b>Legal</b><br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;">
Developing apps for users under 13 years old requires attention to <a href="http://business.ftc.gov/privacy-and-security/children%E2%80%99s-online-privacy">COPPA</a>, which restricts innovation and use of standard practices without offering any real protection for children. With COPPA, you can't associate an installation of the app with any sort of identifier and no custom messages are allowed.<br />
<br />
With the way they've crafted their no custom message rule, there's no free form text allowed anywhere. You can't have a 'name' field where they could put in their full name, but you can have fields for first name and last initial which makes no sense because there's nothing to stop them from just putting their full name into the first name field. By the letter of the rule, kids are not allowed to share their art directly from their kid-friendly art app. They'll just have to hit the home button and open the email app and attach the image from there. The rule does nothing here but throw up a usability speed bump.<br />
<br />
Here are some of the things that you can't do without some sort of identifier:<br />
<ul>
<li>gather anonymized stats to see how users are interacting with your app and where they might be getting stuck</li>
<li>validate in-app purchases on a server (which would help defend against piracy and allows you to deliver content on-demand instead of shipping really large binaries)</li>
<li>allow people to share content without having to create yet another set of account credentials</li>
<li>auto-magically sync content bought through other authorized channels such as your website or a device on another platform</li>
</ul>
Some developers are okay with working under the assumption that they won't get in trouble implementing some things that technically violate COPPA as long as they're not violating the spirit of it and using the gathered info for aggressive direct marketing. I'm not. The whole idea that I might be trying to bring joy and learning to kids one day and get slapped with a $50k fee the next day is nauseating. It's an extra hardship on top of the struggle to be profitable while doing good.<br />
<br />
So why aren't there better educational apps for elementary school kids? It's hard, you don't end up with something cool that you can show off to friends, there's no money in it, and you might get in trouble with the government.</div>Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com3tag:blogger.com,1999:blog-5712169742061717756.post-4922062678636378832011-03-17T11:02:00.000-07:002011-03-17T11:02:37.596-07:00iOS Linebreak FunLearned something the hard way today. You'd think that the order in which properties are set shouldn't matter even in weirdo Objective-C land, but things are different over there.<br />
<br />
The following will wrap long button text, but limit it to two lines.<br />
<div style="color: #713ea3; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"><span style="color: black;"><br />
</span></div><div style="color: #713ea3; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"><span style="color: black;">button.</span>titleLabel<span style="color: black;">.</span>lineBreakMode<span style="color: black;"> = </span><span style="color: #3e1f7c;">UILineBreakModeWordWrap</span><span style="color: black;">;</span></div><div style="color: #713ea3; font: 11.0px Menlo; margin: 0.0px 0.0px 0.0px 0.0px;"><span style="color: black;">button.</span>titleLabel<span style="color: black;">.</span>numberOfLines<span style="color: black;"> = </span><span style="color: #2c2ecf;">2</span><span style="color: black;">;</span></div><div><span style="color: black;"><br />
</span></div><div><span style="color: black;"><div style="color: #713ea3; font: normal normal normal 11px/normal Menlo; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black;"></span></div><div style="color: #713ea3; font: normal normal normal 11px/normal Menlo; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black;"><span class="Apple-style-span" style="font-family: Times;"><span class="Apple-style-span" style="font-size: small;">The following will wrap long button text all over your image and outside the bounds of your button. You'll be scratching your head for a while wondering why the numberOfLines property doesn't have any effect.</span></span></span></div><div style="color: #713ea3; font: normal normal normal 11px/normal Menlo; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black;"><br />
</span></div>button.</span>titleLabel<span style="color: black;">.</span>numberOfLines<span style="color: black;"> = </span><span style="color: #2c2ecf;">2</span><span style="color: black;">;</span><br />
<div style="color: #713ea3; font: normal normal normal 11px/normal Menlo; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="color: black; font-family: Times;"><span class="Apple-style-span" style="font-size: small;"><span class="Apple-style-span" style="font-family: Menlo; font-size: small;"><span class="Apple-style-span" style="font-size: 11px;"></span></span></span></span></div><span class="Apple-style-span" style="color: black; font-family: Times;"><span class="Apple-style-span" style="font-family: Menlo; font-size: small;"><div style="color: #713ea3; font-family: Times; font-size: medium; font: normal normal normal 11px/normal Menlo; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black;"></span></div><div style="color: #713ea3; font: normal normal normal 11px/normal Menlo; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black;">button.</span>titleLabel<span style="color: black;">.</span>lineBreakMode<span style="color: black;"> = </span><span style="color: #3e1f7c;">UILineBreakModeWordWrap</span><span style="color: black;">;</span></div><span style="color: black;"><br />
</span><br />
<div style="color: #713ea3; font-family: Times; font-size: medium; font: normal normal normal 11px/normal Menlo; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span style="color: black;"><br />
</span></div></span></span></div>Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-60611887615031504942011-02-28T13:08:00.000-08:002011-02-28T13:31:24.344-08:00Shop Stats open for business!<a href="http://shopstats.com/">Shop Stats</a> is out of closed beta and now ready for open signups!<br />
<br />
After I made a couple of paid Android apps available on Market, I quickly found out how painful it is to keep track of my sales through Google Checkout. I was spending more time refreshing and counting than coding. Thus, Shop Stats was born. <br />
<br />
With this web service, you can:<br />
<br />
<ul><li>leave the overview page open and it'll auto-refresh with the latest numbers throughout the day</li>
<li>see your sales broken out by state in case your state makes you pay sales tax for digital goods</li>
<li>sanity check your numbers when you fill out your tax forms</li>
<li>see the effect of app update timing</li>
<li>lots more coming...</li>
</ul><div><a href="http://shopstats.com/">Sign up!</a></div>Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-50032140687399333352010-11-06T22:02:00.000-07:002010-11-06T22:02:18.762-07:00Adding a single static fileTo add a single static file to your App Engine application, let's say your robots.txt file, add the following to the <i>handlers</i> section of your app.yaml:<br />
<br />
<br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">- url: /robots.txt</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> static_files: static/robots.txt</span><br />
<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"> upload: static/robots.txt</span><br />
<br />
This assumes your robots.txt file lives in the directory <i>static</i> and that you want to access it from http://<yourdomain>/robots.txt</yourdomain><br />
<br />
My first try after looking at the <a href="http://code.google.com/appengine/docs/python/config/appconfig.html#Static_File_Handlers">docs</a> missed the <i>upload</i> parameter. Totally zoomed in on <i>static_files</i> and figured I was good to go.Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-32659980356237486742010-11-06T03:41:00.000-07:002010-11-06T03:41:34.444-07:00Mac helpersSome Mac-related things that have helped with the day-to-day:<br />
<br />
<ul><li>UTC clock widget - detailed instructions <a href="http://hints.macworld.com/article.php?story=20050430193250534">here</a>. UTC is way easier to work with as a base for date/time normalization, but it's a pain to constantly have to convert in your head (especially if you travel). Modifying the World Clock widget was simple. Remember to look in <root>/Library not <home>/Library and make a backup just in case...</home></root></li>
<li>iTunes control - <a href="http://www.yousoftware.com/tunes/">you control: tunes</a>. I use all of my Spaces and with this free utility I get iTunes music control no matter which Space I'm in (adds controls to your menu bar).</li>
</ul>Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-1743589872398644712010-11-02T01:11:00.000-07:002010-11-02T01:11:32.798-07:00Google App Engine and Cron<div><ol><li>Stagger your cron tasks such that they don't resonate</li>
<li>Limit cron time limits to 2 digits</li>
</ol></div>Looking at the requests/second graph on my GAE dashboard recently, I noticed a pattern of medium peaks at one frequency and a pattern of large peaks at about half that frequency. These were the result of the way I had set up my cron tasks.<div><br />
</div><div>Self-inflicted usage spikes have to be avoided when you need to live within per minute quotas. Because of a natural inclination towards time anchors like whole and half hours, multiple cron tasks were often starting up at the same time even though they all had different intervals. The large spikes were from when they would all start up at the same time. Oops.</div><div><br />
</div><div>Changed the intervals to ones that didn't overlap so frequently and ran in the the error below: </div><div><br />
<div style="font: 11.0px Monaco; margin: 0.0px 0.0px 0.0px 0.0px;">Error parsing yaml file:</div><div style="font: 11.0px Monaco; margin: 0.0px 0.0px 0.0px 0.0px;">Unable to assign value 'every 157 minutes' to attribute 'schedule':</div><div style="font: 11.0px Monaco; margin: 0.0px 0.0px 0.0px 0.0px;">schedule 'every 157 minutes' failed to parse: line 1:8 mismatched input u'7' expecting set None</div><div style="font: 11.0px Monaco; margin: 0.0px 0.0px 0.0px 0.0px;"><br />
</div><div style="font: 11.0px Monaco; margin: 0.0px 0.0px 0.0px 0.0px;"><span class="Apple-style-span" style="font-family: Times, 'Times New Roman', serif;"><span class="Apple-style-span" style="font-size: medium;">Turns out that '<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><span class="Apple-style-span" style="font-size: small;">every n ...</span></span>' is ok and '<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><span class="Apple-style-span" style="font-size: small;">every nn ...</span></span>' is ok, but '<span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;"><span class="Apple-style-span" style="font-size: small;">every nnn ...</span></span>' is not.</span></span></div></div>Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-82037582849733249672010-07-19T10:34:00.000-07:002010-11-02T01:11:49.983-07:00Google App Engine and pytz<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">Here are some notes on my experience with making the Python date/time package </span><i><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">pytz</span></i><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"> available to an application on Google App Engine (GAE).</span><br />
<br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">The default pytz package is generally accepted to be unsuitable for GAE because of the load time. Rodrigo Moraes has created a version called gaepytz that is more suitable to GAE's distributed architecture.<span class="Apple-style-span" style="-webkit-border-horizontal-spacing: 2px; -webkit-border-vertical-spacing: 2px;"></span></span></div><br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><a href="http://code.google.com/p/gae-pytz/"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">http://code.google.com/p/gae-pytz/</span></a></div><div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><a href="http://pypi.python.org/pypi/gaepytz"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">http://pypi.python.org/pypi/gaepytz</span></a></div><br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">I downloaded the zip version from the Python site thinking that I could use zipimport as described at this GAE article: </span><a href="http://code.google.com/appengine/articles/django10_zipimport.html"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">http://code.google.com/appengine/articles/django10_zipimport.html</span></a></div><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">GAE complained about not being able to find my reference to gaepytz. A little tinkering around revealed that the only files necessary are the ones in the pytz folder and that either GAE or zipimport doesn't like hyphens in the zip. So I unzipped the gaepytz zip, then repackage it with:<br />
</span><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;"><i>zip -r gaepytz.zip pytz</i></span><br />
<br />
<div style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">With this, the error saying that it couldn't find the gaepytz file went away and was replaced with the following:</span></div><br />
<pre style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-size: small;"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">IOError: [Errno 20] Not a directory:
'.../gaepytz.zip/pytz/zoneinfo.zip'</span></span></pre><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">Turns out that zipimport doesn't like nested zip files. The next step was to unzip the gaepytz.zip file and just use the pytz folder directly. This meant going from 1 file at 512kB to 6 files at 569kB which isn't the end of the world.</span><br />
<br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">The next bit was to dig into the comments of the source code to figure out how to use the package because <i><span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">import pytz</span></i> clearly wasn't the answer. In the comments of one of the files is the helpful note:</span><br />
<br />
<pre style="margin-bottom: 0px; margin-left: 0px; margin-right: 0px; margin-top: 0px;"><span class="Apple-style-span" style="font-size: small;"><span class="Apple-style-span" style="font-family: 'Courier New', Courier, monospace;">import sys
sys.path.insert(0, 'gaepytz')
from pytz.gae import pytz</span></span></pre><br />
<span class="Apple-style-span" style="font-family: Arial, Helvetica, sans-serif;">You only need the last line since the pytz folder is added directly and no longer in the zip file. A little more work than I was originally counting on, but glad to have it working.</span>Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-66631328500412806872010-04-07T15:39:00.000-07:002010-11-02T01:12:04.165-07:00All views are not created equalFun Android developer tip, custom views will not necessarily behave the same when added through XML instead of Java.<br />
<br />
<b>Summary</b> <br />
getResources().getDisplayMetrics().density will help with a majority of any scale issues.<br />
<br />
<b>Scenario</b><br />
You created an Android game back in October 2008. To this end, you used your old school AWT skills to create a custom view, an animation loop, and a touch event listener to get touches using XY coordinates. Then you went back to your full time job because applications couldn't be sold and there was no advertising platform for Android, yet.<br />
<br />
Fast forward to modern times. You come back to this game and decide to try to add some advertising to support development of a new version of the game. Whoa, there have been 6 new versions of Android released in the meantime and a lot of different phones are now on the market as well. You build your game against 1.6 and 2.1 and test on your phones with 3 different size screens (QVGA, HVGA, WVGA -- you do have one of each, right?). Everything works great. There are a few details to clean up, but in general, cool beans.<br />
<br />
You go to add an ad banner to your app and the example code all points to using XML layouts instead of setting everything up in your Activity. Everything's set up to use XML layouts now et voila. Something's wrong. The ad banner shows up fine, and your game screen shows up fine but touch events are all screwy. Let's say that a circle is drawn around where each touch happens. On the HVGA phone, everything looks as it did before. On the QVGA phone, the circles are way bigger than they should be. On the WVGA phone, the circles are way smaller than they should be. Given that everything worked fine when it was all Java, Android knows how to scale things for you, it just doesn't want to do it for you when you use an XML layout because Android is a good father. Doing something for yourself even when it's already done for you is character building.<br />
<br />
<b>Solution</b><br />
The issue is that you responded to XY touch events with the assumption that XY distance would always be XY distance. This isn't true for different density displays. X pixels on low density screens travels much further than the same X pixels on high density screens. In XML, the general solution for dealing with this is adding <i>dip</i> to the end if things. Density independent pixels... mmm... tasty... Wait, the draw methods only have float arguments. Where's my <i>dip</i> goodness?<br />
<br />
final float scale = getResources().getDisplayMetrics().density;<br />
<br />
Multiply all of your draw coordinates with this scale value and you'll be mostly done.<br />
<br />
<b>Bonus tip</b><br />
If you have <i>xml </i>and <i>xml-hdpi</i> folders and everything works great on Android 1.6+, don't be surprised if things are borked on Android 1.5. When the documentation says that unknown modifiers will be ignored, it doesn't mean you're all set and Cupcake will use <i>xml</i> and ignore <i>xml-hdpi</i>. It means it will ignore the "hdpi" modifier and will consider the two folders equivalent. It will just pick whichever resource it wants to use (read: it will usually choose <i>xml-hdpi </i>out of spite). To fix this, rename <i>xml-hdpi</i> to <i>xml-hdpi-v4</i>. "v4" means "use for API level 4 and higher".<br />
<br />
<b>Super bonus tip</b><br />
If you're using <i>values </i>and <i>values-land</i> to store separate parameters to make a widget look nice in both portrait and landscape orientations, it'll work for Android 1.6+ not not for Android 1.5. Fingers crossed that the 35-40% of Android users on 1.5 today don't have phones that change the home screen to landscape (Sprint, AT&T backflip, Europe, etc.).Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0tag:blogger.com,1999:blog-5712169742061717756.post-29119322786084973452010-03-19T18:53:00.000-07:002010-11-02T01:12:16.471-07:00Tricks to Layout Tricks #1I spent a couple of days learning things about creating custom list rows and decided to share with the Android dev world.<br />
<br />
When I needed to create a custom list item, with an icon to the left and 2 rows of text to the right, I came across <a href="http://android-developers.blogspot.com/2009/02/android-layout-tricks-1.html">Android Layout Tricks #1</a>. Using a relative layout for better performance sounded nice. Defining elements relative to each other so that they automagically look right on various screen sizes sounded really nice. But I wanted to avoid the hard coded row height. That just didn't seem right.<br />
<br />
Like many other developers, I track a bit on the obsessive side. I was sure there must be some way to arrange my elements without any hard coded values so I spent a good couple of days trying out all sorts of attribute combinations. I got the name and number too high, too low, too far apart, rendered in the same space, and one visible but not the other. <br />
<br />
But at the end of the day, I found that you couldn't entirely avoid hard coding. The solution I settled on was to hard code the margins -- this seemed slightly better than a hard coded row height.<br />
<br />
<b>Final lesson:</b> <i>damn</i> the torpedoes, use hard coded margins<br />
<br />
------------------------<br />
<br />
Here are a few other things I ran into which should be useful.<br />
<br />
<b>Tidbit 1:</b> One thing to note about the code in the Android blog post is its use of <span style="font-family: 'Courier New', Courier, monospace;">layout_alignParentTop</span> together with <span style="font-family: 'Courier New', Courier, monospace;">layout_alignParentBottom</span> to center an element. <span style="font-family: 'Courier New', Courier, monospace;">layout_centerVertical</span> is a shorter and more reliable way of achieving the same effect.<br />
<br />
<br />
<b>Tidbit 2:</b> Use <span style="font-family: 'Courier New', Courier, monospace;">LayoutInflater.inflate(layoutId, parent, false)</span> with a non-null value for parent.<br />
<br />
Unmentioned in the blog post is the use of <span style="font-family: 'Courier New', Courier, monospace;">LayoutInflater.inflate</span>. When you create your own <span style="font-family: 'Courier New', Courier, monospace;">ArrayAdapter</span>, you need to use the version that takes <span style="font-family: 'Courier New', Courier, monospace;">(int resource, ViewGroup parent, boolean attachToRoot)</span>. Even though you should set <span style="font-family: 'Courier New', Courier, monospace;">attachToRoot</span> to false, that doesn't mean there shouldn't be a root. Pass through the <span style="font-family: 'Courier New', Courier, monospace;">ViewGroup</span> parent value and don't set it to null. I discovered this in a <a href="http://groups.google.com/group/android-developers/browse_thread/thread/13edf631f29ec80e/d114b06d8464573b?lnk=gst&q=layout+tricks#d114b06d8464573b">forum post</a>.<br />
<br />
<br />
<b>Tidbit 3:</b> Stick with one row view for the list.<br />
<br />
One failed line of research was to use two row views. One view for when both rows of text in the list item were present and one for when only the bottom row was present. Picking the correct view in <span style="font-family: 'Courier New', Courier, monospace;">ArrayAdapter.getView</span> was a snap and everything seemed copacetic. Done and ship.<br />
<br />
Bug report, uh oh. It turned out that one of my users was having issues when there were more than N items in the list. I had tested and it looked ok; so I tested it again and it still looked ok. It was tempting to disregard this bug report, but it was written in complete sentences so that gave it a fair bit of credence.<br />
<br />
With yet another round of testing, I paid careful attention to the data I was entering and found the issue. Before, I had only really checked the number of list items and the order of the phone numbers. For the names, I usually entered "a". Adding different names for each number revealed an Android optimization technique for rendering lists and the cause of the bug. The first page of the list always looked ok, but after scrolling, the subsequent list item would show names that had already been displayed when it should have been using the "no name" view. Whaaa?<br />
<br />
Object creation and garbage collection are expensive operations, so it looks like the Android engineers decided to optimize things by 1. only instantiating as many row objects as needed to show the first page of items and then 2. reusing row objects that moved off the top and putting them at the bottom. The assumption here is that each row view is the same kind of object. If you use more than one kind of row view in a list, then the recycling algorithm will give you one kind of view when your code has asked for another. So if your list might scroll, and of course it will, stick to one row view.Steve Songhttp://www.blogger.com/profile/11388354319302890572noreply@blogger.com0