Mike Classic

Android: Populating Spinner With Strings And Id

Jul/25/2014

Ever needed to reference an object when selected in a default implementation of a Spinner widget? Most likely you have. Your Spinner is populated with an ArrayList of strings, so how can you map it to the object it represents?

The user may select a student's name, but how do we preserve the key to look it up in the original collection upon selection in the Spinner?

Thanks to a kind soul on StackOverflow, here is a simple solution, which doesn't resort to iterating through all Student objects with the first name that matches the user's selection.

Let's assume your original collection of students is a HashMap, defined with an Integer key and a String value. So let's pretend it looks like this:

HashMap<Integer, String> mStudents = new HashMap<Integer, String>() {{
    put(0, "Bob");
    put(1, "Christine");
}};

This way you don't have to keep the ArrayList in a certain order in the Spinner, and you don't even have to rely on Integer-based keys either.

Well the Spinner is only going to accept an ArrayList of String values. So how can we convert this to an ArrayList while preserving our keys for lookup later?

private static class StringWithTag {
  public String string;
  public Object tag;

  public StringWithTag(String string, Object tag) {
    this.string = string;
    this.tag = tag;
  }

  @Override
  public String toString() {
    return string;
  }
}

Instead of passing ArrayList<String>, you pass instances of this class to the ArrayAdapter class which will be attached to the Spinner.

The Spinner will execute .toString() on every item in the collection, to which we've overridden that method to display the String-based value accordingly.

Spinner spinner = (Spinner) v.findViewById(R.id.spinner);
/* Create your ArrayList collection using StringWithTag instead of String. */
List<StringWithTag> itemList = new ArrayList<StringWithTag>();

/* Iterate through your original collection, in this case defined with an Integer key and String value. */
for (Entry<Integer, String> entry : mStudents.entrySet()) {
  Integer key = entry.getKey();
  String value = entry.getValue();

  /* Build the StringWithTag List using these keys and values. */
  itemList.add(new StringWithTag(value, key));
}

/* Set your ArrayAdapter with the StringWithTag, and when each entry is shown in the Spinner, .toString() is called. */
ArrayAdapter<StringWithTag> spinnerAdapter = new ArrayAdapter<StringWithTag>(getActivity(), android.R.layout.simple_spinner_item, itemList);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(spinnerAdapter);

And this is how we can retrieve it upon item selection.

public void onItemSelected(AdapterView<?> parent, View view, int position,
    long id) {
    StringWithTag swt = (StringWithTag) parent.getItemAtPosition(position);
    Integer key = (Integer) swt.tag;
    doSomethingWith(mStudents.get(key));
}

Collection Class: Array Mapping

May/07/2014

Thank you, Laravel, for your Illuminate package. Thank you for the Collection class.

Recently I had a situation where I had to query a database, return some results, and convert the entries from objects into strings. In vanilla PHP, one can do this with something as simple as array_map.

I figured, since I was working with the Laravel framework, that I'd use the Illuminate\Support\Collection class, as I knew it was quite easy to use, handy, and a great enhancement to PHP's array functions.

While I was digging through the Collection class source, I noticed different array mapping functions, so I had to determine which was best to use for my situation.

Each()

This method simply executed the array_map function with no frills. It didn't save it either. So basically the user uses it to perform external actions, such as building a new array or object with it, used outside of the existing Collection instance.

    /* Collection method: */
    public function each(Closure $callback) {
        array_map($callback, $this->items);
    }

    /* Sample usage. */
    $percentages = array();
    $collection->each(function($item) use (&$percentages)
    {
        $percentages[] = $item * 100;
    });

Map()

The map method also uses array_map, but passes you the array keys as well, in case you need to use them. Additionally, it returns a new Collection instance. This could be useful if, say, you want to further manipulate your new array but want to preserve the existing instance as well.

    /* Collection method: */
    public function map(Closure $callback)
    {
        return new static(array_map($callback, $this->items, array_keys($this->items)));
    }

    /* Sample usage. */
    $newCollection = $collection->map(function($item, $key)
    {
        return array($key => $item + 1);
    });

Transform()

And finally, we come to transform. This one was my saviour tonight. It allowed me to manipulate the Collection in place. That is, it also uses array_map but manipulates the array in-place. This allowed me, in my example, to convert an array of objects into an array of strings without having to waste memory by creating a second array.

    /* Collection method: */
    public function transform(Closure $callback)
    {
        $this->items = array_map($callback, $this->items);

        return $this;
    }

    /* Sample usage. */
    /* Registrations before:
     * $registrations[0] = new \stdClass() { public $registration_id = '1234' };
     * Registrations after:
     * $registrations[0] = '1234';
     */
    $registrations->transform(function($item)
    {
        return (string) $item->registration_id;
    });

Laravel: Mass Inserts Using DB Query Builder

Apr/20/2014

When passing arrays for mass insert into the DB QueryBuilder, make sure each array entry has the same fields to populate, no extras. This may be intuitive for you, but it was not for me.

Symptoms

The problem manifested itself during database seeding. Here is a sample schema for the type of table I was building and seeding.

Schema::create('services', function(Blueprint $table)
{
    $table->primary('id');
    $table->boolean('auxiliary')->default(false);
    $table->decimal('rate');
});

I had a seeder class which had an array of entries to insert. This is basically an array of arrays, each nested array represents one table row. This is demonstrated below:

class ServicesTableSeeder extends Seeder {
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('services')->delete();

        $entries = [
            [
                'rate' => 50.00,
                'description' => 'Cheaper Service'
            ],
            [
                'rate' => 100.00,
                'description' => 'Some Service',
                'auxiliary' => true
            ],
            /* ... */
        ];

        DB::table('services')->insert($entries);
    }
}

As you can see, one row had more fields to populate than the previous. I had thought that the query builder would accomodate for this, but it doesn't. I'd run this seeder, and the table would simply not populate at all, with no errors thrown.

The solution I came up with was to run array inserts where the rows/entries all populate the same fields. So if you have rows that populate more/less/different fields, group them together in a separate array of entries, as such:

class ServicesTableSeeder extends Seeder {
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        DB::table('services')->delete();

        $entries = [
            [
                'rate' => 50.00,
                'description' => 'Cheaper Service'
            ],
            [
                'rate' => 60.00,
                'description' => 'Medium Service'
            ]
        ];

        $entries2 = [
            [
                'rate' => 100.00,
                'description' => 'Some auxiliary service',
                'auxiliary' => true
            ],
            [
                'rate' => 90.00,
                'description' => 'Explicitly non-auxiliary service',
                'auxiliary' => false
            ]
        ];

        DB::table('services')->insert($entries);
        DB::table('services')->insert($entries2);
    }
}

Notice how the two arrays of entries have the same fields to populate.

The Journey to PHPStorm

Mar/25/2014

A series of PHPStorm customization posts

Let's go back about twenty years. Sometime in, say, 1992. Back when I was using Netscape, and web pages all had grey backgrounds and still had Gopher links. I was enthralled with this newfound technology, the Internet and the World Wide Web. So cool!

HTML

Eventually, I wanted to create my own web pages. This started off with Notepad and local storage of pages. Although I used Notepad for a while, eventually WYSIWYG editors started popping up. After some initial resistance, I decided to jump in and join the trend. I started off with Hotdog Web Editor, if anyone remembers that. Meh. I wasn't head over heels in love.

After that, I got into Adobe products. I started using Dreamweaver. I kind of enjoyed it, but then I was still lukewarm to the whole WYSIWYG thing. Build web pages with a GUI that create the shittiest code known to mankind, aside from, possibly, offshore contractors.

At least Dreamweaver allowed me to build web pages but also code in PHP. Coming from text editors such as Notepad and UltraEdit, that was enough for me. What I really liked about Dreamweaver was that it had FTP built into it, making it easy to keep the pages on the server in sync.

Of course, much later I realized no one should ever edit live!

From HTML to PHP

When it came to PHP development, I started off with PHP 3.

Please don't hold that against me. That's how things were back then. Web development was still in its infancy, it had barely learned how to walk by that point. Perl was still the most popular way to execute server-side scripts via CGI. Nothing wrong with Perl in and of itself — as a matter of fact, it is has very powerful text processing capabilities.

At this point I was still using Dreamweaver but more as a text editor than a web page editor. I was doing PHP in it, I was even doing pre-.NET ASP (VBscript) in it. Ugh. Fun times.

NetBeans

At some point after bouncing around from Dreamweaver, to UltraEdit, to Notepad++, sprinkled generously with Vim (and even Pico/Nano at one point,) I found NetBeans which had a PHP plugin. It was at this point that I started understanding why IDE's were beneficial. I had used IDE's before, but not for PHP. Code completion, code intelligence, even if these things were not perfected, they were a breath of fresh air in what was a stale workflow for me at the time.

NetBeans was one of my first introductions to code completion, PHPDoc-style comments, and VCS, among other things. Although I still used text editors, NetBeans became my primary editor at this point. Notepad++ started to feel jealous.

I had heard of SublimeText 2/3, I had heard of PHPStorm, but I never gave them any chance. I cannot say why, exactly. I had seen them in plenty of tutorial videos, mostly on Tuts+ and Laracasts.

Until one day recently.

I installed a trial version of PHPStorm, tweaked the settings, and quite quickly fell in love with the keyboard-centric workflow. Wow. Double-shift, CTRL+N, CTRL+SHIFT+N, it was just like in Jeffrey Way's videos! Small window pops up, you type a partial class name, and the file opens. Magic!

SCSS file watchers? Beautiful! I used to set up a file watcher in Linux using the watch command in order to compile my CSS. Now I don't even have to leave my IDE.

In this series of posts, I intend to explore the various features and customizations one can perform with PHPStorm. This is as much for me as it is anyone else. It will encourage me, and hopefully at least one of you, to explore PHPStorm and learn new things to enhance your experience.

Laravel: App Environment Detection

Mar/20/2014

When I develop Laravel apps, I do so in several different environments. As per the Laravel Docs, by default, the environment in which your app runs is defined in bootstrap/start.php as an array which maps hostnames of machines to whichever environments you want them to run.

You can make as many or as few environments as you want. It's recommended to create three separate environments, but again, there's no rule set in stone. These environments are: development, staging, & production. I used to have that set up as such:

$env = $app->detectEnvironment(array(
    'development' => array('vagrant-machine'),
    'staging' => array('staging.somehost.xyz'),
    'production' => array('www.yourapp.xyz')
));

One can also have multiple machine names mapped to an environment, so if you wanted to have two machines set up in your development environment, you could implement like so:

$env = $app->detectEnvironment(array(
    'development' => array('vagrant-machine', 'another-machine'),
    'staging' => array('staging.somehost.xyz'),
    'production' => array('www.yourapp.xyz')
));

It's a beautiful thing, being this simple and configurable.

Some people like to use environment variables so that they don't have to keep remembering a bunch of hostnames, especially if they deploy to multiple servers or instances. This can be accomplished by providing a Closure instead of an array to Laravel's $app->detectEnvironment() method:

$env = $app->detectEnvironment(function()
{
    return getenv('LARAVEL_ENV');
});

At this point, you could set your environment variable through Apache's .htaccess file:

SetEnv LARAVEL_ENV "development"

It's as simple as that.

Call me crazy, or stupid, but I actually have a mixture of both. If the environment variable has not been set, then fall back to the array of hostnames to determine environment.

$env = $app->detectEnvironment(function()
{
    // Set up the environments fall-back array
    $environments = array(
        'vagrant' => array('vagrant-box'),
        'local' => array('my-laptop')
    );

    // Attempt to determine via environment variable first
    $env = getenv('LARAVEL_ENV');
    if (!empty($env)) {
        return $env;
    } else {
        /*
         * Environment variable empty or invalid, proceed to sort
         * through fall-back hostname array
         */
        $hostname = gethostname();
        foreach ($environments as $environment => $hosts) {
            foreach ((array) $hosts as $host) {
                if (!strcasecmp($host, $hostname)) {
                    // Found, return mapped hostname, we're outta here
                    return $environment;
                }
            }
        }

        // Nothing matched, in array nor within environment veriable(s)
        return 'production';
    }
});