Welcome to End Point’s blog

Ongoing observations by End Point people.

Tuesday, May 21, 2013

Making Python Code a Little Bit Cleaner

Posted by Szymon Guz

When you develop a program in a group of programmers, it is really important to have some standards. Especially helpful are standards of naming things and formatting code. If all team members format the code in the same way and use consistent names, then it is much easier to read the code. This also means that the team works faster.

The same rules apply when you develop software in Python.

For Python there is a document which describes some of the most desirable style features for Python code Style Guide for Python Code. However there are some problems with that, as even the standard Python library has some libraries which are not consistent. This shouldn’t be an excuse for your team to be inconsistent as well. Ensuring that the code is nice and readable is worth working for a moment on that.

In Python there are two tools which I use for writing code in Python – Python style guide checker and Python code static checker.

pep8

Program pep8 is a simple tool checking Python code against some of the style conventions in PEP 8 document.

Installation

You can install it within your virtual environment with simple:

pip install pep8

Usage

Let’s test the pep8 command on such an ugly Python file named test.py:

"this is a very long comment line this is a very long comment line this is a very long comment line"
def sth  (  a ):
    return  "x"+a
def sth1 ( a,b,c):
    a+b+c

The basic usage of the program is:

pep8 test.py

The above command prints:

test.py:1:80: E501 line too long (100 > 79 characters)
test.py:2:1: E302 expected 2 blank lines, found 0
test.py:2:11: E201 whitespace after '('
test.py:2:14: E202 whitespace before ')'
test.py:2:8: E211 whitespace before '('
test.py:3:16: E225 missing whitespace around operator
test.py:4:1: E302 expected 2 blank lines, found 0
test.py:4:11: E201 whitespace after '('
test.py:4:13: E231 missing whitespace after ','
test.py:4:15: E231 missing whitespace after ','
test.py:4:9: E211 whitespace before '('
test.py:5:6: E225 missing whitespace around operator
test.py:5:8: E225 missing whitespace around operator
test.py:6:1: W391 blank line at end of file

Configuration

Pep8 is highly configurable. The most important options allow to choose which errors should be ignored. For this there is an argument --ignore. There is also one thing in PEP8 document, which I don’t agree with. This document states that the length of the line shouldn’t be bigger than 80 characters. Usually terminals and editors I use are much wider and having 100 characters doesn’t make your program unreadable. You can set the allowed length of your line with --max-line-length.

So if I want to ignore the errors about empty lines at the end of file and set maximum line length to 100, then the whole customized command is:

pep8 --ignore=W391 --max-line-length=100  test.py 

The output is different now:

test.py:2:1: E302 expected 2 blank lines, found 0
test.py:2:11: E201 whitespace after '('
test.py:2:14: E202 whitespace before ')'
test.py:2:8: E211 whitespace before '('
test.py:3:16: E225 missing whitespace around operator
test.py:4:1: E302 expected 2 blank lines, found 0
test.py:4:11: E201 whitespace after '('
test.py:4:13: E231 missing whitespace after ','
test.py:4:15: E231 missing whitespace after ','
test.py:4:9: E211 whitespace before '('
test.py:5:6: E225 missing whitespace around operator
test.py:5:8: E225 missing whitespace around operator

Config file

The same effect can be achieved using a config file. PEP8 searches for this file at the project level, the file must be named .pep8 or setup.cfg. If such a file is not found, then it looks for a file ~/.config/pep8. Only the first file is taken into consideration. After finding a file, it looks for a pep8 section in, if there is no such section, then no custom settings are used.

To have the same settings as in the above example, you can create a file .pep8 in the project directory with the following content:

[pep8]
ignore = W391
max-line-length = 100

The list of all all possible errors you can find at PEP8 documentation page.

Statistics

Another nice option which I use for checking the code is --statistics. It prints information about the type and number of problems found. I use it along with -qq option which causes pep8 to hide all other informations. The sort -n 1 -k -r part sorts the pep8 output in reverse order (biggest numbers come first) by first column treating that as numbers:

pep8 --statistics -qq django | sort -k 1 -n -r

The first 10 lines of the above command run against Django 1.5.1 code look like:

4685    E501 line too long (80 > 79 characters)
1718    E302 expected 2 blank lines, found 1
1092    E128 continuation line under-indented for visual indent
559     E203 whitespace before ':'
414     E231 missing whitespace after ','
364     E261 at least two spaces before inline comment
310     E251 no spaces around keyword / parameter equals
303     E701 multiple statements on one line (colon)
296     W291 trailing whitespace
221     E225 missing whitespace around operator

pylint

Pylint is a program very similar to pep8, it just checks different things. The pylint’s goal is to look for common errors in programs and find potential code smells.

Installation

You can install pylint in a similar way as pep8:

pip install pylint

Usage

Usage is similar as well:

pylint --reports=n test.py 

Notice there is --reports argument. Without it, the output is much longer and quiet messy.

The output of the above command is:

No config file found, using default configuration
************* Module test
C:  1,0: Line too long (100/80)
C:  2,0:sth: Invalid name "a" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C:  2,0:sth: Missing docstring
C:  2,12:sth: Invalid name "a" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
C:  4,0:sth1: Comma not followed by a space
def sth1 ( a,b,c):
            ^^
C:  4,0:sth1: Invalid name "a" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C:  4,0:sth1: Invalid name "b" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C:  4,0:sth1: Invalid name "c" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C:  4,0:sth1: Missing docstring
C:  4,11:sth1: Invalid name "a" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
C:  4,13:sth1: Invalid name "b" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
C:  4,15:sth1: Invalid name "c" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
W:  5,4:sth1: Statement seems to have no effect

Configuration

For pylint you can decide which problems should be ignored as well. If I want to ignore some errors, you have to know its number first. You can get the number in two ways, you can check at pylint errors list or add the message number with argument --include-ids=y:

pylint --reports=n --include-ids=y test.py 
No config file found, using default configuration
************* Module test
C0301:  1,0: Line too long (100/80)
C0103:  2,0:sth: Invalid name "a" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C0111:  2,0:sth: Missing docstring
C0103:  2,12:sth: Invalid name "a" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
C0324:  4,0:sth1: Comma not followed by a space
def sth1 ( a,b,c):
            ^^
C0103:  4,0:sth1: Invalid name "a" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C0103:  4,0:sth1: Invalid name "b" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C0103:  4,0:sth1: Invalid name "c" for type argument (should match [a-z_][a-z0-9_]{2,30}$)
C0111:  4,0:sth1: Missing docstring
C0103:  4,11:sth1: Invalid name "a" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
C0103:  4,13:sth1: Invalid name "b" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
C0103:  4,15:sth1: Invalid name "c" for type variable (should match [a-z_][a-z0-9_]{2,30}$)
W0104:  5,4:sth1: Statement seems to have no effect

Now I know the number of the problem I want to ignore, let’s assume it is C0103, then I can ignore it with:

pylint --reports=n --include-ids=y --disable=C0103 test.py 
No config file found, using default configuration
************* Module test
C0301:  1,0: Line too long (100/80)
C0111:  2,0:sth: Missing docstring
C0324:  4,0:sth1: Comma not followed by a space
def sth1 ( a,b,c):
            ^^
C0111:  4,0:sth1: Missing docstring
W0104:  5,4:sth1: Statement seems to have no effect

Config file

Pylint also supports setting the options in a config file. This config file can be a little bit complicated, and I think the best way is to let pylint generate the file, this can be done with the --generate-rcfile argument:

pylint --reports=n --include-ids=y --disable=C0103 --generate-rcfile > .pylint

This will create config file with all default settings and the changes from the command line.

To use the new config file, you should use the --rcfile argument:

pylint --rcfile=.pylint test.py

Remarks

Pylint is great – sometimes even too great.

I usually ignore many of the errors, as too often the changes needed to satisfy pylint are not worth time spending on them. One of common problems found by pylint is that the variable name is too short. It has a rule that all the names should have between 2 and 30 characters. There is nothing bad with one letter variable, especially when it is something like Point(x, y) or it is a small local variable, something like for i in xrange(1,1000).

However on the other hand when a variable has much broader usage, or it should have some meaningful name to have code easier to read, it is a good idea to change the code.

For me it is good to have pylint checking such errors, so I don’t want pylint to ignore them. Sometimes it is OK to have code which violates those rules, so I just ignore them after ensuring that it is on purpose.

Friday, May 17, 2013

Adventures with using Ruby 2.0 and libreadline

Posted by Kamil Ciemniewski

I was asked to develop a prototype app for one of our clients lately. The basis for this app was an old Rails app:

  • Rails 3.2.8
  • RailsAdmin
  • MySQL
  • rbenv + ruby-build

I wanted to upgrade the stack to work with latest toys all cool kids are so thrilled about. I also didn’t have Rails console facility at my disposal since the Ruby version installed on the development machine hadn’t been compiled against libreadline.

Not having root or sudo access on the machine I embarked on a sligthly hacky journey to make myself a better working environment.

Ruby 2.0

After reading Mike Farmer’s blog post about Ruby 2.0 and tons of other material about it on the Internet, I wanted to get a feeling of how faster & greater the new Ruby is. It’s always great also to stay up-to-date with latest technologies. It’s great for me as a developer, and more importantly - it’s great for our clients.

Importance of libreadline in development with Ruby

To be productive developing any Rails-based application, we have to have Rails-console available at any moment. It serves a multitude of purposes. It’s also a great scratch-pad when developing methods.

While you don’t need your Ruby to support libreadline for basic uses of irb, you need it when using with Rails.

Installing Ruby 2.0.0 with rbenv (ruby-build)

If you’ve installed ruby-build some time ago, chances are that you need to update it in order to be able to install latest build of Ruby 2.0.0

To do it:

cd ~/.rbenv/plugins/ruby-build
git pull

And you should be able now to have available latest Ruby build to install:

rbenv install 2.0.0-p195

If you want to install Ruby compiled with support for libreadline, you have to have it installed in your system before compiling the build with rbenv install.

If you have access to root or sudo on your system, the easiest way is to e. g:

on Debian-related Linuxes:

apt-get install libreadline-dev

or on Fedora:

yum install readline-devel

Installing libreadline from sources

In my case - I had to download sources and compile them myself. Luckily the system had all needed essential packages installed for building it.

wget "ftp://ftp.cwru.edu/pub/bash/readline-6.2.tar.gz"
tar xvf readline-6.2.tar.gz
cd readline-6.2
./configure --prefix=/home/kamil/libs
make
make install

I had to specify –prefix option pointing at the path where I wanted the libreadline library to be installed after compilation.

Then, I was able to actually build Ruby with readline support “on”:

CONFIGURE_OPTS="--with-readline-dir=/home/kamil/libs" rbenv install 2.0.0-p195

Notice: I was making myself a development environment and compiling from sources was my last resort. It is not a good practice for production environments.

Last thing I needed to do was to get rb-readline working with the project I was working on.

It turnes out that latest rb-readline doesn’t play well with latest Ruby. Also, when using Ruby 2.0.0 one have to explicitely specify it in the Gemfile, or else it won’t be loaded for the console.

Gemfile:

gem 'rb-readline', '~> 0.4.2'

This still isn’t perfect

While this setup works, it won’t let you use arrow keys. The irb process crashes quickly after even first try to navigate through the text.

For some reason, after upgrading Ruby, the RailsAdmin stylesheets stopped working. I noticed that they are being served with comments which should be replaced by other stylesheets like:

/* ...
*= require_self
*= require_tree .
*/

I had to update Rails version in the Gemfile to have my admin back:

Gemfile:

gem 'rails', '3.2.13'

Console:

bundle

Last thing I wanted to do, was to try if I could upgrade Rails even further and have a working Rails4 setup. This was impossible unfortunately since RailsAdmin isn’t yet compatible with it as stated here.

I conclude that latest Ruby is quite usable right now. If you don't mind the quirks with the readline - you're pretty safe to upgrade. This assumes though that your app doesn't use any incompatible elements.

The main Ruby site describes them like so:

There are five notable incompatibilities we know of:

  • The default encoding for ruby scripts is now UTF-8 [#6679]. Some people report that it affects existing programs, such as some benchmark programs becoming very slow [ruby-dev:46547].
  • Iconv was removed, which had already been deprecated when M17N was introduced in ruby 1.9. Use String#encode, etc. instead.
  • There is ABI breakage [ruby-core:48984]. We think that normal users can/should just reinstall extension libraries. You should be aware: DO NOT COPY .so OR .bundle FILES FROM 1.9.
  • #lines, #chars, #codepoints, #bytes now returns an Array instead of an Enumerator [#6670]. This change allows you to avoid the common idiom “lines.to_a”. Use #each_line, etc. to get an Enumerator.
  • Object#inspect does always return a string like #<ClassName:0x…> instead of delegating to #to_s. [#2152]
  • There are some comparatively small incompatibilities. [ruby-core:49119]

Wednesday, May 15, 2013

Breaking Up Your Asset Pipeline

Posted by Mike Farmer

The Rails Asset Pipeline to me is kind of like Bundler. At first I was very nervous about it and thought that it would be very troublesome. But after a while of using it, I realized the wisdom behind it and my life got a lot easier. The asset pipeline is fabulous for putting all your assets into a single file, compressing them, and serving them in your site without cluttering everything up. Remember these days?

<%= javascript_include_tag 'jquery.validate.min.js','jquery.watermark.min.js','jquery.address-1.4.min','jquery.ba-resize.min','postmessage','jquery.cookie','jquery.tmpl.min','underscore','rails','knockout-1.3.0beta','knockout.mapping-latest' %>

A basic component of the Asset Pipeline is the manifest file. A manifest looks something like this

// Rails asset pipeline manifest file
// app/assets/javascript/application.js

//= require jquery
//= require jquery_ujs
//= require_tree .

For a quick rundown on how the Asset Pipeline works, I highly recommend taking a few minutes to watch Railscast episode 279. But there are a few things that I'd like to point out here.

First, notice that the name of the file is application.js. This means that all the javascript specified in the manifest file will be compiled together into a single file named application.js. In production, if specified, it will also be compressed and uglified. So rather than specifying a really long and ugly javascript_include_tag, you just need one:

<%= javascript_include_tag "application.js" %>

Second, this means that if you only have one manifest file in your application, then all of your javascript will be loaded on every page, assuming that your javascript_include_tag is loading in the head of your layout. For small applications with very little javascript, this is fine, but for large projects where there are large client-side applications, this could be a problem for performance. For example, do you really need to load all of ember.js or backbone.js or knockout.js in the admin portion of your app when it isn't used at all? Granted, these libraries are pretty small, but the applications that you build that go along with them don't need to be loaded on every page.

So I thought there should be a way to break up the manifest file so that only javascript we need is loaded. The need for this came when I was upgrading the main functionality of a website to use the Backbone.js framework. The app was large and complex and the "single-page-application" was only a part of it. I didn't want my application to load on the portion of the site where it wasn't need. I looked high and low on the web for a solution to this but only found obscure references so I thought I would take some time to put the solution out there cleanly hoping to save some of you from trying to figure it out on your own.

The solution lies in the first point raised earlier about the asset pipeline. The fact is, you can create a manifest file anywhere in your assets directory and use the same directives to load the javascript. For example, in the app/assets/javascripts/ directory the application.js file is used by default. But it can be named anything and placed anywhere. For my problem, I only wanted my javascript application loaded when I explicitly called it. My directory structure looked like this:

|~app/
| |~assets/
| | |+fonts/
| | |+images/
| | |~javascripts/
| | | |+admin/
| | | |+lib/
| | | |+my_single_page_app/
| | | |-application.js

My application.js file looks just like the one above but I removed the require_tree directive. This is important because now I don't want all of my javascript to load from the application.js. I just want some of the big stuff that I use all over like jQuery. (Ok, I also added the underscore library in there too because it's just so darn useful!) This file is loaded in the head of my layout just as I described above.

Then, I created another manifest file named load_my_app.js in the root of my_single_page_app/. It looks like this:

//= require modernizr
//= require backbone-min
//= require Backbone.ModelBinder
//= require my_single_page_app/my_app
//= require_tree ./my_single_page_app/templates
//= require_tree ./my_single_page_app/models
//= require_tree ./my_single_page_app/collections
//= require_tree ./my_single_page_app/views
//= require_tree ./my_single_page_app/utils

Then in my view that displays the single page app, I have these lines:

<% content_for :head do %>
  <%= javascript_include_tag "my_single_page_app/load_my_app.js" %>
<% end %>

Now my single page application is loaded into the page as my_single_page_app/load_my_app.js by the Asset Pipeline and it's only loaded when needed. And a big bonus is that I don't need to worry about any of the code that I wrote or libraries I want to use interfering with the rest of the site.

Selenium Testing File Uploads in Django Admin

Posted by Adam Vollrath

The Django framework version 1.4 added much better integration with Selenium for in-browser functional testing. This made Test-Driven Development an even more obvious decision for our new Liquid Galaxy Content Management System. This went very well until we needed to test file uploads in the Django admin interface.

A browser's file upload control has some unique security concerns that prevent JavaScript from setting its value. Trying to do so may raise INVALID_STATE_ERR: DOM Exception 11. Selenium's WebDriver may sometimes send keystrokes directly into the input element, but this did not work for me within Django's admin interface.

To work around this limitation, Ryan Kelly developed a Middleware to emulate successful file uploads for automated testing. This middleware inserts additional hidden fields into any forms sent to the client. Setting their value causes a file upload to happen locally on the server. (I used a slightly newer version of this Middleware from another project.)

However, Selenium intentionally will not interact with hidden elements. To work around this, we must send JavaScript to be executed directly in the browser using WebDriver's execute_script method. You can see an example of this here.

        self.browser.execute_script("document.getElementsByName('fakefile_storage')[0].value='placemark_end_point.kml'")

This is a lot of hoops to jump through, but now we have functional tests for file uploads and their post-upload processing. Hopefully the Selenium or Django projects can develop a better-supported method for file upload testing.

Monday, May 13, 2013

Foreign Data Wrappers

Posted by Joshua Tolley

Original images from Flickr user jenniferwilliams

One of our clients, for various historical reasons, runs both MySQL and PostgreSQL to support their website. Information for user login lives in one database, but their customer activity lives in the other. The eventual plan is to consolidate these databases, but thus far, other concerns have been more pressing. So when they needed a report combining user account information and customer activity, the involvement of two separate databases became a significant complicating factor.

In similar situations in the past, using earlier versions of PostgreSQL, we've written scripts to pull data from MySQL and dump it into PostgreSQL. This works well enough, but we've updated PostgreSQL fairly recently, and can use the SQL/MED features added in version 9.1. SQL/MED ("MED" stands for "Management of External Data") is a decade-old standard designed to allow databases to make external data sources, such as text files, web services, and even other databases look like normal database tables, and access them with the usual SQL commands. PostgreSQL has supported some of the SQL/MED standard since version 9.1, with a feature called Foreign Data Wrappers, and among other things, it means we can now access MySQL through PostgreSQL seamlessly.

The first step is to install the right software, called mysql_fdw. It comes to us via Dave Page, PostgreSQL core team member and contributor to many projects. It's worth noting Dave's warning that he considers this experimental code. For our purposes it works fine, but as will be seen in this post, we didn't push it too hard. We opted to download the source and build it, but installing using pgxn works as well:

$ env USE_PGXS=1 pgxnclient install mysql_fdw
INFO: best version: mysql_fdw 1.0.1
INFO: saving /tmp/tmpjrznTj/mysql_fdw-1.0.1.zip
INFO: unpacking: /tmp/tmpjrznTj/mysql_fdw-1.0.1.zip
INFO: building extension
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -fpic -I/usr/include/mysql -I. -I. -I/home/josh/devel/pg91/include/postgresql/server -I/home/josh/devel/pg91/include/postgresql/internal -D_GNU_SOURCE -I/usr/include/libxml2   -c -o mysql_fdw.o mysql_fdw.c
mysql_fdw.c: In function ‘mysqlPlanForeignScan’:
mysql_fdw.c:466:8: warning: ‘rows’ may be used uninitialized in this function [-Wmaybe-uninitialized]
gcc -O2 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Wendif-labels -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -fpic -shared -o mysql_fdw.so mysql_fdw.o -L/home/josh/devel/pg91/lib -L/usr/lib  -Wl,--as-needed -Wl,-rpath,'/home/josh/devel/pg91/lib',--enable-new-dtags  -L/usr/lib/x86_64-linux-gnu -lmysqlclient -lpthread -lz -lm -lrt -ldl
INFO: installing extension
< ... snip ... >

Here I'll refer to the documentation provided in mysql_fdw's README. The first step in using a foreign data wrapper, once the software is installed, is to create the foreign server, and the user mapping. The foreign server tells PostgreSQL how to connect to MySQL, and the user mapping covers what credentials to use. This is an interesting detail; it means the foreign data wrapper system can authenticate with external data sources in different ways depending on the PostgreSQL user involved. You'll note the pattern in creating these objects: each simply takes a series of options that can mean whatever the FDW needs them to mean. This allows the flexibility to support all sorts of different data sources with one interface.

The final step in setting things up is to create a foreign table. In MySQL's case, this is sort of like a view, in that it creates a PostgreSQL table from the results of a MySQL query. For our purposes, we needed access to several thousand structurally identical MySQL tables (I mentioned the goal is to move off of this one day, right?), so I automated the creation of each table with a simple bash script, which I piped into psql:

for i in `cat mysql_tables`; do
    echo "CREATE FOREIGN TABLE mysql_schema.$i ( ... table definition ...)
        SERVER mysql_server OPTIONS (
            database 'mysqldb',
            query 'SELECT ... some fields ... FROM $i'
        );"
done

In a step not shown above, this script also consolidates the data from each table into one, native PostgreSQL table, to simplify later reporting. In our case, pulling the data once and reporting on the results is perfectly acceptable; in other words, data a few seconds old wasn't a concern. We also didn't need to write back to MySQL, which presumably could complicate things somewhat. We did, however, run into the same data validation problems PostgreSQL users habitually complain about when working with MySQL. Here's an example, in my own test database:

mysql> create table bad_dates (mydate date);
Query OK, 0 rows affected (0.07 sec)

mysql> insert into bad_dates values ('2013-02-30'), ('0000-00-00');
Query OK, 2 rows affected (0.02 sec)
Records: 2  Duplicates: 0  Warnings: 0

Note that MySQL silently transformed '2013-02-30' into '0000-00-00'. Sigh. Then, in psql we do this:

josh=# create extension mysql_fdw;
CREATE EXTENSION

josh=# create server mysql_svr foreign data wrapper mysql_fdw options (address '127.0.0.1', port '3306');
CREATE SERVER

josh=# create user mapping for public server mysql_svr options (username 'josh', password '');
CREATE USER MAPPING

josh=# create foreign table bad_dates (mydate date) server mysql_svr options (query 'select * from test.bad_dates');
CREATE FOREIGN TABLE

josh=# select * from bad_dates ;
ERROR:  date/time field value out of range: "0000-00-00"

We've told PostgreSQL we'll be feeding it valid dates, but MySQL's idea of a valid date differs from PostgreSQL's, and the latter complains when the dates don't meet its stricter requirements. Several different workarounds exist, including admitting that '0000-00-00' really is wrong and cleaning up MySQL, but in this case, we modified the query underlying the foreign table to fix the dates on the fly:

SELECT CASE disabled WHEN '0000-00-00' THEN NULL ELSE disabled END,
    -- various other fields
    FROM some_table

Fortunately this is the only bit of MySQL / PostgreSQL impedance mismatch that has tripped us up thus far; we'd have to deal with any others we found individually, just as we did this one.

Friday, May 10, 2013

Lanyrd: Finding conferences for the busy or travel-weary developer

Posted by Jeff Boes

Recently I had planned to attend a nearby technical conference on a weekend, but my plans fell through. As a result, my supervisor encouraged me to find a replacement, but having been out of the "free T-shirt and all the presentations you can stay awake through" circuit for many years, I didn't have any ideas of where to start.

I wanted to filter the conferences by topic: no sense attending a Web Development conference if all the presentations were far afield from what I do; I'm not a Ruby developer at present, and have no immediate plans to become one, so not much point in attending a deep exploration of that topic.

I also wanted to stay local: if there's something I can get to by car in a day, I'd prefer it.

I stumbled upon Lanyrd.com almost by accident: it's a sharp, well-engineered central point for data about upcoming conferences on a dizzying array of topics. Not just software: library science, economics, photography, water management, social media, and medicine were represented in just the one day on which I wrote this post.

I subscribed to about a dozen topics, limiting each to the USA, and Lanyrd immediately suggested 46 events in which I might be interested.

You can connect your Twitter or LinkedIn account with Lanyrd, which seems to offer a way to see when your friends and colleagues will be attending conferences. I wasn't able to confirm that, as the overlap of my LinkedIn account with Lanyrd results in only one person, but I imagine that feature would be more valuable for others.

Thursday, May 9, 2013

Dynamically adding custom radio buttons in Android

Posted by Zed Jensen

I've been writing a timesheet tracking app for End Point. In working on various features of this app, I've had more than a few problems to work through, since this project is one of my first on Android and I've never used many of the Android features that I'm now using. One particularly fun bit was setting up a scrollable list of radio buttons with numbers from 0 - 23 (no, we don't often have people working 23 hours on a project in a day, but just in case!) when the user is creating an entry, as a prettier and more backward-compatible alternative to using a number picker to indicate how many hours were spent on this particular job.

In Android, view layouts are usually defined in XML like this:

layout/activity_hour_picker.xml

 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent" >  
   <Button android:id="@+id/button"  
     android:layout_height="wrap_content"  
     android:layout_width="wrap_content"  
     android:onClick="doStuff"  
     android:text="Hello World!" />  
 </RelativeLayout>  

Simple. But, as you can imagine, not so fun when you're adding a list of 24 buttons - so, I decided to add them dynamically in the code. First, though, a ScrollView and RadioGroup (ScrollView only allows one child) need to be defined in the XML, no point in doing that programmatically. Let's add those:

layout/activity_hour_picker.xml

 <?xml version="1.0" encoding="utf-8"?>  
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent" >  
    <HorizontalScrollView   
         android:id="@+id/hour_scroll_view"  
         android:layout_width="match_parent"  
         android:layout_height="wrap_content"  
         android:fillViewport="true"  
         android:scrollbars="none" >  
         <RadioGroup  
             android:id="@+id/hour_radio_group"  
             android:layout_width="wrap_content"  
             android:layout_height="match_parent"  
             android:orientation="horizontal">  
             // This is where our buttons will be  
         </RadioGroup>  
     </HorizontalScrollView>  
 </RelativeLayout>  

Okay. So, now, in our Activity, we need to override onCreate if we haven't already and add the following code:

src/com/example/HourPickerActivity.java

 @Override  
 public void onCreate(Bundle icicle) {  
   super.onCreate(icicle);  
   setContentView(R.layout.activity_hour_picker);  // This adds the views from the XML we wrote earlier
   ViewGroup hourButtonLayout = (ViewGroup) findViewById(R.id.hour_radio_group);  // This is the id of the RadioGroup we defined
   for (int i = 0; i &lt; RANGE_HOURS; i++) {  
     RadioButton button = new RadioButton(this);  
     button.setId(i);  
     button.setText(Integer.toString(i));  
     button.setChecked(i == currentHours); // Only select button with same index as currently selected number of hours  
     button.setBackgroundResource(R.drawable.item_selector); // This is a custom button drawable, defined in XML   
     hourButtonLayout.addView(button);  
   }    
 }  

And this is what we get:

It scrolls horizontally like we want, but there's a problem - we don't want the default radio button selector showing up, since we've already got custom button graphics showing. And we can't forget that we're still missing the code to make everything within the RadioGroup work properly - In other words, the buttons won't do anything when a user clicks them. So, let's add a listener to each button as it's created:

src/com/example/HourPickerActivity.java

 button.setId(i);  
 button.setBackgroundResource(R.drawable.item_selector); // This is a custom button drawable, defined in XML
 button.setOnClickListener(new OnClickListener() {  
         @Override  
         public void onClick(View view) {  
             ((RadioGroup) view.getParent()).check(view.getId());  
             currentHours = view.getId();  
         }  
     });  
 button.setText(Integer.toString(i));

So now the currently selected value is ready in our static variable currentHours for whenever the user is finished. Now we need to get rid of the standard radio button graphics. The solution I found is to use selector XML, with just one item that points to a transparent drawable:

drawable/null_selector.xml

 <?xml version="1.0" encoding="utf-8"?>  
 <selector xmlns:android="http://schemas.android.com/apk/res/android" >  
   <item android:drawable="@android:color/transparent" />  
 </selector>  

Set each button to use it (and to center the text) like this (R.drawable.null_selector is our selector XML):

src/com/example/HourPickerActivity.java

 button.setText(Integer.toString(i));
 button.setGravity(Gravity.CENTER);  
 button.setButtonDrawable(R.drawable.null_selector);  
 button.setChecked(i == currentHours); // Only select button with same index as currently selected number of hours  

Now, let's see how this all has pulled together.

There - that looks much better! And it works great.