Brian Hardy - Big Nerd Ranch Tue, 19 Oct 2021 17:46:55 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Customizing Android ListView Rows by Subclassing https://bignerdranch.com/blog/customizing-android-listview-rows-by-subclassing/ https://bignerdranch.com/blog/customizing-android-listview-rows-by-subclassing/#respond Mon, 21 Apr 2014 22:51:52 +0000 https://nerdranchighq.wpengine.com/blog/customizing-android-listview-rows-by-subclassing/ Every Android programmer will at some point customize a ListView row by creating their own layout and populating it with data. When that happens, you'll probably reach for the Holder pattern. But the Holder pattern is clumsy and full of boilerplate, and we can do better. In this post, we explore an alternative that uses a subclass of RelativeLayout to encapsulate the customization work.

The post Customizing Android ListView Rows by Subclassing appeared first on Big Nerd Ranch.

]]>

Every Android programmer will at some point customize a ListView row by creating their own layout and populating it with data. When that happens, you’ll probably reach for the Holder pattern. But the Holder pattern is clumsy and full of boilerplate, and we can do better. In this post, we explore an alternative that uses a subclass of RelativeLayoutto encapsulate the customization work.

The Goal

For demonstration purposes, we’ll create a typical simple custom ListView row with an ImageView and two TextViews arranged inside a RelativeLayout parent. You can see what it looks like in the screen shot below. You can find the code for this example project on GitHub.

custom listview

When creating custom views for a ListView or other AdapterView, we have a few requirements:

  1. Use a custom layout to define the arrangement of child views.

  2. Take advantage of view recycling when scrolling.

  3. Efficiently identify and populate child views with data.

The Problems with the Holder Pattern

Since this is such a common problem, patterns have emerged. One implementation you see described all over the Internet is called “the holder pattern.” In a nutshell, it entails these steps:

  • Create a layout for your row view.

  • Create a “holder” class with fields to store child views of the row.

  • For each new inflated row, create a holder instance and assign its fields to the results of calling findViewById(int) for each child view.

  • Set the instance of the holder as the top-level view’s tag with setTag(Object).

  • Reuse (and cast) the holder object for reused row views.

I don’t like this pattern, for several reasons:

  • It puts too much responsibility in the Adapter’s getView(...) method.

  • The “holder” class is typically just boilerplate code and creating it/setting it up is a chore.

  • The view’s tag property requires casting to the correct holder type, which feels kludgy.

  • It violates encapsulation, since the adapter/holder has to know about the internals of the view representing each item in the list.

So, rather than complaining about it all the time, I’ll propose an alternative: subclassing!

Customizing Using a Subclass

Instead of creating a generic view, a holder class and an Adapter implementation that knows too much about how to wire them all together, we will use a subclass of RelativeLayout for the root view of our custom layout. I call this class ItemView. Item is the generic model object that represents data within our list using three properties: an image URL, a title and a description.

    public class Item {
        private String mImageUrl;
        private String mTitle;
        private String mDescription;
        // constructor, getters and setters elided
    }

ItemView is responsible for translating an Item into views to render onscreen. As a user of ItemView (in the Adapter subclass), I want my job to be as simple as possible. I really just need to do two things:

  1. Create or reuse an instance of ItemView for a row.

  2. Associate the current row’s Item with the ItemView.

You can see the API for this in action in the ItemAdapter class:

    public class ItemAdapter extends ArrayAdapter<Item> {
        public ItemAdapter(Context c, List<Item> items) {
            super(c, 0, items);
        }
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ItemView itemView = (ItemView)convertView;
            if (null == itemView)
                itemView = ItemView.inflate(parent);
            itemView.setItem(getItem(position));
            return itemView;
        }
    }

There are two interesting lines of code here: first, we call ItemView.inflate(ViewGroup), a static method, to return an instance of ItemView if we don’t have one to reuse. Then, we populate the view with the data for the current item using setItem(Item). All of the details of how this inflation and population happen are encapsulated within ItemView.

ItemView acts as its own “holder” by using member variables to store references to its significant child views.

    public class ItemView extends RelativeLayout {
        private TextView mTitleTextView;
        private TextView mDescriptionTextView;
        private ImageView mImageView;
        ...
    }

The inflate(ViewGroup) static method makes it simple to create a properly-configured ItemView in code while still taking advantage of an XML layout resource for easy configuration-based customization.

    public static ItemView inflate(ViewGroup parent) {
        ItemView itemView = (ItemView)LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_view, parent, false);
        return itemView;
    }

It uses the parent argument (in this case the ListView) to get a Context and inflate the R.layout.item_view layout resource, returning the root ItemView found there. If we look at the layout, we can see that it is just one element:

    <com.bignerdranch.android.listitemviewdemo.ItemView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="5dp" />

All we have in that file is an instance of ItemView with some basic padding specified. Note in particular that there are no child views here. Where are the ImageView and the two TextViews? To find them we have to look at the constructor for ItemView, where it inflates a secondary layout for its children.

    public ItemView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        LayoutInflater.from(context).inflate(R.layout.item_view_children, this, true);
        setupChildren();
    }

This constructor will (eventually) be called during the process of inflating the layout containing the ItemView element. When that happens, we call through to the superclass constructor to take care of the standard attributes, and then we inflate R.layout.item_view_children to add the child views of interest to the current ItemView instance. This layout looks like this:

    <?xml version="1.0" encoding="utf-8"?>
    <merge
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:layout_width="match_parent"
      android:layout_height="wrap_content" >
      <ImageView
        android:id="@+id/item_imageView"
        android:background="@android:color/darker_gray"
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_margin="5dp"
        android:contentDescription="@string/item_imageView_contentDescription"
        />
      <TextView
        android:id="@+id/item_titleTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/item_imageView"
        android:text="title text"
        />
      <TextView
        android:id="@+id/item_descriptionTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/item_imageView"
        android:layout_below="@id/item_titleTextView"
        android:layout_marginTop="5dp"
        android:text="description text"
        />
    </merge>

The root element is a merge tag, which means that during inflation, each of the children of that tag will be added as children of the parent argument passed to the inflate(...) method in the constructor. After that is done, we call our private setupChildren() method to wrap up the work of calling findViewById(int) and associating the child views with the appropriate member variables.

    private void setupChildren() {
        mTitleTextView = (TextView) findViewById(R.id.item_titleTextView);
        mDescriptionTextView = (TextView) findViewById(R.id.item_descriptionTextView);
        mImageView = (ImageView) findViewById(R.id.item_imageView);
    }

At this point, ItemView is capable of doing everything the “holder” class would typically do. It will cache these child-view references in its own member variables, and there is no need to use the “tag” for anything.

For convenience, we also provide the setItem(Item) method for callers to use to populate the child views with an Item’s data:

    public void setItem(Item item) {
        mTitleTextView.setText(item.getTitle());
        mDescriptionTextView.setText(item.getDescription());
        // TODO: set up image URL
    }

That’s about all there is to this pattern. While we had to create two layout files (instead of one) and a static convenience method for inflation, look at all the advantages we get for it:

  • The Adapter implementation is greatly simplified.

  • The ItemView can be created easily in code or in an XML layout file.

  • Any future customizations or configuration-specific changes to the layout of ItemView can be handled entirely within that class’ implementation and layout files.

  • There are no extra “holder” classes or objects created during the process.

Subclassing works

The next time you find yourself reaching for the holder pattern, consider subclassing instead. The encapsulation it offers allows you to freely customize your list row view without your Adapter or other code having to worry about the details, and the performance should be equally as good as the holder pattern during view reuse.

If you want to see some examples of alternative ways to implement this functionality, check out Pavel Dudka’s post that was linked up in Android Weekly back in March. I enjoyed reading the discussion of the techniques presented there, but I feel the pattern shown here offers a nice marriage of less code, good performance and easy customization.

The post Customizing Android ListView Rows by Subclassing appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/customizing-android-listview-rows-by-subclassing/feed/ 0
Xcode Breakpoint Wizardry for Debugging https://bignerdranch.com/blog/xcode-breakpoint-wizardry-for-debugging/ https://bignerdranch.com/blog/xcode-breakpoint-wizardry-for-debugging/#respond Thu, 07 Nov 2013 19:55:15 +0000 https://nerdranchighq.wpengine.com/blog/xcode-breakpoint-wizardry-for-debugging/

Every great developer should know how to use a debugger. Xcode has an excellent debugger UI that wraps LLDB (or, if you’re living in the past, GDB), giving you access to all the standard tricks like breakpoints, stepping in and out and around your code, and stack frame details.

The post Xcode Breakpoint Wizardry for Debugging appeared first on Big Nerd Ranch.

]]>
Every great developer should know how to use a debugger. Xcode has an excellent debugger UI that wraps LLDB (or, if you’re living in the past, GDB), giving you access to all the standard tricks like breakpoints, stepping in and out and around your code, and stack frame details.
Many times in your debugging adventures, the basics will suffice. But occasionally, a hairy problem will surface that demands more finesse. Imagine, for example, that you’re debugging something that only happens every five minutes on a timer, and then triggers some multi-threaded gobbledy-gook where timing is critical to reproducing a bug.
I don’t know about your work environment, but in mine, five minutes is an eternity, and I will get distracted by at least two shiny objects while waiting for the timer to fire. What I need is something to grab my attention again.

Audible Breakpoints!

When you want your breakpoint to have a bit more oomph, Xcode can play a sound and snap your focus back to the problem. To configure this, set a breakpoint on your favorite line of code (or method definition), right click on it and choose “Edit Breakpoint…” from the menu.
edit-breakpoint-menu
Xcode will pop up the breakpoint editor window, which has a ton of cool options.
edit-breakpoint-window
To make a breakpoint alert you with a sound, you’ll need to add an “action” to the breakpoint. Xcode offers several to choose from.
breakpoint-actions
Select the “Sound” action, and the window will update, allowing you to select from a list of sounds to be triggered when your breakpoint hits.
breakpoint-sounds
“Glass” is a nice choice since it’s pretty loud and clear. Remember, you want to be alerted here.
By default, your breakpoint will still pause the execution of your app after playing the sound. For timing-sensitive situations, or things that happen frequently, this may be undesirable.
Check the “Automatically continue after evaluation” box to have Xcode keep your app chugging along after the breakpoint is evaluated. Marvel at your now-audible alert that some point in your code has been hit!
Also keep in mind that the presence of such a breakpoint will introduce brief hiccups in your app’s performance, especially when running on an iOS device. When the debugger encounters this point in the code, it pauses execution of the thread long enough to switch contexts and execute whatever actions are attached to the breakpoint. This means that if you are debugging very time-sensitive code, your breakpoint actions could have adverse effects on the app’s ability to reproduce something consistently. A good rule of thumb is to keep the action’s execution as short as possible.
Another feature of breakpoint actions is the ability to execute a shell command when the breakpoint is hit. I’m sure you can think of tons of great uses for this feature, but in the spirit of keeping things audible, try this:
Command: say
Arguments: -v, Zarvox, "Your breakpoint %B has been hit %H times"

Backtraces: How Did We Get Here?

Sometimes you may find yourself debugging code in a method or function that has many entry points throughout the code. In these situations, you might wonder how the app got to that point (or why it’s getting to that point so darned often).
Under normal circumstances, a regular breakpoint that pauses the app will show the stack trace for the current thread in the Xcode debug navigator, and this may be all you need. Again, though, there arise situations where timing is critical or you don’t want the app to pause every time it hits the breakpoint.
This is where another breakpoint action can come in handy: Debugger Command. LLDB offers many commands, but one that is useful in knowing how you arrived is “bt”, which will print out the current thread’s stack trace (backtrace) to the console.
bt-debugger-command
Combine this with “Automatically Continue” and some particularly hot or confusing code path, and you can then see console output of where your code was hit, interspersed with any other output you may have, for example from NSLog.

    2013-10-14 14:28:07.047 MyStuff[62951:a0b] About to call super viewDidLoad
    * thread #1: tid = 0x3cccc1, 0x00003076 MyStuff`-[BNRMasterViewController viewDidLoad](self=0x08988fa0, _cmd=0x009bad27) + 102 at BNRMasterViewController.m:35, queue = 'com.apple.main-thread, stop reason = breakpoint 1.1
        frame #0: 0x00003076 MyStuff`-[BNRMasterViewController viewDidLoad](self=0x08988fa0, _cmd=0x009bad27) + 102 at BNRMasterViewController.m:35
        frame #1: 0x003409a8 UIKit`-[UIViewController loadViewIfRequired] + 696
        frame #2: 0x00340c44 UIKit`-[UIViewController view] + 35
        frame #3: 0x0036b339 UIKit`-[UINavigationController rotatingSnapshotViewForWindow:] + 52
        frame #4: 0x00694910 UIKit`-[UIClientRotationContext initWithClient:toOrientation:duration:andWindow:] + 420
        frame #5: 0x00270ea2 UIKit`-[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:isRotating:] + 1495
        frame #6: 0x002708c6 UIKit`-[UIWindow _setRotatableClient:toOrientation:updateStatusBar:duration:force:] + 82
        frame #7: 0x00270798 UIKit`-[UIWindow _setRotatableViewOrientation:updateStatusBar:duration:force:] + 117
        frame #8: 0x00270820 UIKit`-[UIWindow _setRotatableViewOrientation:duration:force:] + 67
        frame #9: 0x0026f8ba UIKit`__57-[UIWindow _updateToInterfaceOrientation:duration:force:]_block_invoke + 120
        ...
    2013-10-14 14:28:07.048 MyStuff[62951:a0b] Called super viewDidLoad

If you are debugging a multi-threaded part of your application, you can also use the command bt all to show the stack trace for every thread when the breakpoint is hit. You can also print out a limited number of stack frames by adding a number to the command, like bt 10.

Symbolic Breakpoints

Sometimes you want to set a breakpoint that will hit in a certain place in the app, but you may not have direct access to the code in question. Imagine trying to debug a third-party library for which you only have a binary, or something in the iOS frameworks. Without access to the code, you can’t set a breakpoint based on file names or line numbers, because you don’t know them.
This is where symbolic breakpoints can really save your bacon. Using one of these, you can set a breakpoint based on a symbol, like a method or function name, regardless of where that name might appear in the code.
Imagine you would like to know when viewDidLoad is called on any UIViewController instance. You can set up a symbolic breakpoint for -[UIViewController viewDidLoad] and it will be triggered any time that method is called. That will be, for example, any time a subclass implementation calls [super viewDidLoad].
To create a symbolic breakpoint in Xcode, click the + button in the lower-left toolbar within the Breakpoint Navigator and choose “Add Symbolic Breakpoint…”
add-breakpoint-menu
The new breakpoint will be added to the list, and a window will pop up asking you to fill in the details. Add the name of your symbol and press return to create the new breakpoint.
UIViewController-viewDidLoad-symbolic-breakpoint
If you run an iOS app with this breakpoint enabled, it will pause each time that method is called. You may find out some interesting details about the guts of iOS using breakpoints like this.
You can add symbolic breakpoints that refer to a method (selector) in a specific class, as above, or you can be more generic with your symbol and just provide a name, like viewDidLoad. In this case, Xcode will do something amazing: after you add the breakpoint, it will look for all matches for that name in the app and linked libraries and make them “child” breakpoints of the one you just created.
Here’s what it looks like when you create a symbolic breakpoint for viewDidLoad:
viewDidLoad-symbolic-breakpoint
Fascinating! Look at all of those “hidden” subclasses of UIViewController that live within the frameworks. We didn’t even have to break out class-dump!

Need to know how to keep your development project on track and in budget? Download your eBook.

Watchpoints

Watchpoints are a tool you can use to monitor the value of a variable or memory address for changes and trigger a pause in the debugger when changes happen. They can be very helpful in identifying problems with the state of your program that you might not know precisely how to track down.
For example, imagine you have built a user interface with two buttons and two labels. When you click one of the buttons, the label should update to reflect the number of times you have clicked that button.
click-counting-ui
To track the click count for each button, you’ll use a property like button1ClickCount. For each button, you’ll have an action method that increments the count property and updates the appropriate label. You create these properties and write the action methods.

    - (IBAction)button1Clicked:(id)sender {
        self.button1ClickCount++;
        self.button1ClickCountLabel.text = [self textForButton:1 clickCount:self.button1ClickCount];
    }
    - (IBAction)button2Clicked:(id)sender {
        self.button2ClickCount++;
        self.button2ClickCountLabel.text = [self textForButton:2 clickCount:self.button2ClickCount];
    }
    - (NSString *)textForButton:(NSUInteger)buttonNumber
                     clickCount:(NSUInteger)clickCount {
        return [NSString stringWithFormat:@"Button %d clicked %d times", buttonNumber, clickCount];
    }

To make the action methods trigger in response to the button clicks, you dutifully wire them up in Interface Builder. Then you run your app, and click each button a few times. But, there’s a problem: no matter which button you click, you see only the first button’s count update!
How can you debug this? In this trivial example, of course you could set breakpoints in each of the action methods and ensure that they are getting triggered appropriately. But imagine that the code that is incrementing the counts is really some complex logic that lives in another class with a much more complicated API. When you don’t know where or when a variable is being changed, you can set a watchpoint.
It makes sense here to set up a watchpoint for the value of the button 1 click count. Watchpoints can be set on the value of variables, but button1ClickCount was set up as a property. How can you watch it? You can rely on the automatic synthesized instance variable that backs up the property: _button1ClickCount.
To set the watchpoint, you need to be paused in the debugger within a stack frame that has the variable you want to watch in scope. For ease of demonstration, put a breakpoint in the button1Clicked: action method and trigger it by clicking the first button. Xcode will pause the app and then you can create the watchpoint in one of two ways: using the Xcode GUI or the LLDB console.
To use the GUI, navigate to the Variables View, expose the variable you want to watch, and right-click (control-click) on the name of it. In the menu that appears, select “Watch _button1ClickCount”.
set-watchpoint-menu
If you prefer, you can instead type in the LLDB console to set the watchpoint:

    (lldb) watch set variable _button1ClickCount
    Watchpoint created: Watchpoint 1: addr = 0x0b9a1ca4 size = 4 state = enabled type = w
        declare @ 'MyStuff/MyStuff/BNRDetailViewController.m:52'
        watchpoint spec = '_button1ClickCount'
        new value: 10

By default, a watchpoint watches for “write” occurrences on the variable you specify. This means that when the variable’s value is set, the watchpoint will hit.
With this watchpoint set, you can click either of the buttons and see it get triggered:
watchpoint-hit
You can also see that LLDB helpfully spits out the old and new values of the variable in the console:

    Watchpoint 1 hit:
    old value: 10
    new value: 11

In this contrived example, the root cause of the problem is that button 2’s ”Touch Up Inside” action was wired incorrectly to the button1Clicked: method. It should be apparent, however, that watchpoints can help you in situations where changes are happening and you don’t quite have a handle on why.
You can learn more about watchpoints in the LLDB Tutorial and by typing help watch in the LLDB console. One important detail to note: watchpoints are not saved between executions of your program, so if you need to set a watchpoint repeatedly you should save the command for setting it in another file and paste it into the debugger console when appropriate.
You can also use a combination of techniques discussed here to help: set a breakpoint that hits early in the program’s execution where the variable you want to watch is in scope, and make the action of that breakpoint an LLDB command to set the watchpoint.

Getting to the (Break)Point

With Xcode and LLDB, a wealth of debugging options are at your disposal. Knowing when and how to use these powerful tools can really take the bite out of difficult bugs in your code.
Do you have any other great debugger tips and tricks? Feel free to post them in the comments!

The post Xcode Breakpoint Wizardry for Debugging appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/xcode-breakpoint-wizardry-for-debugging/feed/ 0
Ask Big Nerd Ranch https://bignerdranch.com/blog/ask-big-nerd-ranch/ https://bignerdranch.com/blog/ask-big-nerd-ranch/#respond Wed, 19 May 2010 14:57:28 +0000 https://nerdranchighq.wpengine.com/blog/ask-big-nerd-ranch/

Let’s face it. We all have questions. Why is the sky blue? What shirt will I wear today? Why does mommy spend so much time on the phone with “the handyman?” Thanks to the Big Nerd Ranch, there is now a place to ask these questions, and more.

The post Ask Big Nerd Ranch appeared first on Big Nerd Ranch.

]]>

Let’s face it. We all have questions. Why is the sky blue? What shirt will I wear today? Why does mommy spend so much time on the phone with “the handyman?” Thanks to the Big Nerd Ranch, there is now a place to ask these questions, and more.

OK, truthfully speaking, you should really consider asking us questions about programming in Cocoa or Cocoa Touch. You know, for the Mac, or the iPhone, or the iPad. Do not ask us about your wireless router problems, please.

We’ve teamed up with the fine folks at InformIT to bring you an ongoing series where we answer the best of these questions. We’ve been doing it since February! Go ahead, ask us something!

Here’s a summary of the posts thus far…

Juan Pablo Claude kicked things off with a post on Retain Counts for Cocoa and iPhone Programmers.

Next, Jeremy Sherman tackled a tough one: Detecting that QuickTime Has Exhausted Its Stream.

Steven Degutis came through with Adding Python Scripting to Cocoa Apps.

And just today, I, Brian Hardy, posted about Rotating an iPhone View Around a Point.

The post Ask Big Nerd Ranch appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/ask-big-nerd-ranch/feed/ 0
Find Us at WWDC https://bignerdranch.com/blog/find-us-at-wwdc/ https://bignerdranch.com/blog/find-us-at-wwdc/#respond Sat, 06 Jun 2009 18:48:44 +0000 https://nerdranchighq.wpengine.com/blog/find-us-at-wwdc/

As you probably already know, this week marks Apple’s WWDC 2009 Conference in sunny San Francisco, California.

The post Find Us at WWDC appeared first on Big Nerd Ranch.

]]>

As you probably already know, this week marks Apple’s WWDC 2009 Conference in sunny San Francisco, California.

Like all good Apple nerds, several of us from the Big Nerd Ranch will be attending, and we’d love to see you there. Look for Juan Pablo Claude, Joe Conway, Gautam Godse, Brian Hardy, Scott Ritchie and Alex Silverman sporting jaunty cowboy hats wherever they go. If there is some annoying jerk blocking your view of the keynote with their chapeau, it’s probably one of us!

See you at WWDC!

The post Find Us at WWDC appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/find-us-at-wwdc/feed/ 0
Get Your WWDC sessions into iCal or Google Calendar https://bignerdranch.com/blog/get-your-wwdc-sessions-into-ical-or-google-calendar/ https://bignerdranch.com/blog/get-your-wwdc-sessions-into-ical-or-google-calendar/#respond Tue, 26 May 2009 17:36:33 +0000 https://nerdranchighq.wpengine.com/blog/get-your-wwdc-sessions-into-ical-or-google-calendar/

Update: Apple has since removed the date and time information from the JSON data. This code will no longer work. It was fun while it lasted, no?

The post Get Your WWDC sessions into iCal or Google Calendar appeared first on Big Nerd Ranch.

]]>

Update: Apple has since removed the date and time information from the JSON data. This code will no longer work. It was fun while it lasted, no?

For those of you attending this year’s sold-out WWDC conference in San Francisco, choosing which sessions to attend can be a daunting task. It’s especially daunting considering that Apple still hasn’t officially announced the schedule for these sessions.

Fortunately, Apple was kind enough to format the session data as JSON (available here) and in this trove of data are dates and times for all of the sessions. Thanks to a tip from the excellent Jeff LaMarche, we can process the data and format it in a more calendar-friendly way.

Jeff’s post uses Ruby to format the data in HTML, which is fine for viewing, but not so good for integration with anything else. I decided to run with that theme and convert the code to output an iCalendar format containing all of the session data. Using this script, you can redirect the output to a file and then import that file into Apple’s iCal, Google Calendar, or whatever other client you fancy.

To run this script, you’ll need to install a couple of Ruby gems if you don’t already have them, like so:

$ sudo gem install json icalendar

Once you have those installed (the above instructions work well on most Unix-like systems), you can then run the following script to generate the iCalendar output. Redirect it to a file called, for example, wwdc_sessions_2009.ics, and you can then import that file into your calendar application.

This code is licensed under the public domain, so use it freely. In case the following does not show up well for you, here is a version in Pastie.

Disclaimer: these times have not been officially announced, so the Big Nerd Ranch takes no responsibility for you missing your favorite session or otherwise imperiling your WWDC experience. You’ve been warned.

require 'rubygems'
require 'open-uri'
require 'json'
require 'icalendar'

sessions_data = JSON.parse(open("http://developer.apple.com/wwdc/data/sessions.json").read)["SessionsData"]

calendar = Icalendar::Calendar.new

sessions_data.each do |session|
  calendar.event do
    dtstart DateTime.parse(session["time"][0]["lower"]).new_offset(-7.0/24)
    dtend DateTime.parse(session["time"][0]["upper"]).new_offset(-7.0/24)
    summary session["title"]
    description session["description"]
    location session["room"]
    categories ((session["focus"] || []) << session["level"] << session["type"]).compact.uniq
  end
end

puts calendar.to_ical

The post Get Your WWDC sessions into iCal or Google Calendar appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/get-your-wwdc-sessions-into-ical-or-google-calendar/feed/ 0