Introducing TwitterAspirin: an AS3 Twitter API painkiller

 

A couple months ago, I started working on a Twitter component for my current project at Adobe. I went into this knowing I’d have to finally face the beast… OAuth. Just about every well-known Twitter client out there uses Basic Auth—and for a reason. It’s easy, what the user expects, and gives your app more credibility—there’s no requirement to leave to authenticate through the browser like with OAuth.

About five or six months ago, Twitter decided to enforce the transition. From then on, any application that uses the API must use OAuth in order to see “via [your app]” on tweets published with it—otherwise, it would display “via API.” Since “via” is where apps get probably 90% of their referrals, this was a big deal. Luckily for me, DestroyTwitter existed before that time and Twitter decided not to push the change on the veteran apps. Recently, however, the bad news spread that Basic Auth would be deprecated in June. This means every Twitter app must transition to the pain that is OAuth.

After developing the MAX Companion this past fall and now the more generalized version, I found myself rewriting the Twitter component each time. After a while, the Twitter API code I wrote for DestroyTwitter began to merge with the actual implementation, so it was no longer a standalone library that could easily be used by other projects. These past few months, I’ve been learning a great deal about framework architecture and design patterns. It has led me to realize I need to start fresh.

With all that being said, I’d like introduce a library I started working on two days ago. I call it TwitterAspirin. It’s an AS3 Twitter API library that eases the pain, providing developers with a very powerful tool for communicating with Twitter. Though it’s still a newborn at the moment, I see potential already. The library is built on RobotLegs and uses AS3 Signals instead of events. It’s hosted on GitHub, so the source code will always be available to the public. And, after last night’s commit, its OAuth functionality is complete. Here’s how to use it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package {
	import com.destroytoday.twitteraspirin.Twitter;
 
	import flash.display.Sprite;
 
	public class Test extends Sprite {
		// set application consumer key and secret
		public var twitter:Twitter = new Twitter(consumerKey, consumerSecret);
 
		public function Test() {
			// add signal listeners
			twitter.oauth.requestTokenSignal.add(requestTokenHandler);
			twitter.oauth.accessTokenSignal.add(accessTokenHandler);
			twitter.oauth.verifyAccessTokenSignal.add(verifyAccessTokenHandler);
		}
 
		// click the 'Authorize' button to get the request token
		protected function authorizeClickHandler():void {
			twitter.oauth.getRequestToken();
		}
 
		// upon receiving the request token, open Twitter in the browser to authorize
		protected function requestTokenHandler(oauth:OAuth, token:OAuthToken):void {
			navigateToURL(new URLRequest(oauth.getAuthorizeURL()));
		}
 
		// return with the provided pin and click the 'Activate' button to get the access token
		protected function activateClickHandler():void {
			twitter.oauth.getAccessToken(pin);
		}
 
		// upon receiving the access token, verify it
		protected function accessTokenHandler(oauth:OAuth, token:OAuthToken):void {
			oauth.verifyAccessToken(token);
		}
 
		// done
		protected function verifyAccessTokenHandler(oauth:OAuth, token:OAuthToken):void {
		}
	}
}

As you can see, it’s extremely easy to use. Not only that, it provides great flexibility. Many libraries are simple to implement, but don’t allow the developer access to certain aspects of the process. With TwitterAspirin, each method returns the loader involved with the operation, giving developers the ability to listen for errors, cancel the operation, or attain the raw API data. The library also uses loader pools to recycle instances, so you can submit a tweet and, while it’s loading, submit another—you don’t have to wait for the first operation to finish. Then, once the operation is complete, the loader is disposed and returned to the pool, which optimizes performance and memory usage.

I’m really excited to see where TwitterAspirin goes and I have nothing but great expectations. Be sure to follow along with development and fork whenever you like.

Taking a break from DestroyTwitter

 

It has been exactly one year since I decided to enter the Twitter app world with DestroyTwitter. Within this year, there has been 45 public releases and over 300,000 installs. After working tirelessly on it, all the while juggling college and a job, it’s time to take a break. As much as I love developing DestroyTwitter, I want to experience a life away from the computer for those hours after 5 pm.

Anyone who knows me has probably seen me in front of a computer more often than not. And with every year that goes by, I feel one step closer to burning out. I want to be able to code for as long as I can, but I know it isn’t healthy to maintain my current pace. In college, the morning/day/night work schedule made sense—I was surrounded by peers who worked around the clock as well. Now that I have a full-time job and a family, it’s time to balance the work with a period of downtime and recreation.

I will continue to respond to emails regarding DestroyTwitter as best I can, but I have no plans to post the source code. If you have any questions, feel free to ask them in the comments. It has been a fun year and I can’t thank you enough for contributing to the project.

DestroyTwitter 2.0 Preview: Account canvas

 

DestroyTwitter 2.0 Preview: Account canvas

In DestroyTwitter 2.0, the Account canvas gets a major overhaul. As much as it was nice to change your name, bio, and location in 1.x, I found most people only used it once, if at all. I also discovered that more users were interested in seeing their own profile and viewing it in the People canvas made it a pain, constantly needing to reload.

Because of these issues, the Account canvas in 2.0 extends the People canvas. It displays the active user’s info, including API status, in semi-realtime. This means any time you login, tweet, send a DM, receive a DM, follow someone or unfollow someone, this info is updated without using an additional API call. The API status is actually updated any time DestroyTwitter receives any data from the Twitter API without using an additional call. Also, when you click a user’s icon or username in a tweet, if it’s the active account, DestroyTwitter will know to go to the Account canvas rather than the People canvas.

This screenshot does lack a few buttons I plan to add, such as logout, but it’s mostly all done. I’m really excited about these fine-tunings and improvements over 1.x. Do realize I’m working every night to get this out the door. It’s coming along, but will endure a solid private testing period. I’ll make an announcement soon regarding how to sign up. In the meantime, hold tight.

DestroyTwitter 2.0 Preview: Improved item rendering

 

DestroyTwitter 2.0 Preview: Tweet recycling

The biggest feature in DestroyTwitter 2.0 is multiple accounts. Because of this, a serious amount of planning went into memory optimization. I didn’t want usage to double with each account added, so I adopted a few approaches that worked beyond my wildest expectations. One technique I used is improved item rendering. A few weeks back, I wrote a post about scrolling methods. The current one used in DestroyTwitter 1.7.2 uses preallocation and masking. It’s great for scrolling speed, but awful for instantiation speed and memory usage.

In 2.0, I adopted what I call fixed-height smooth recyclable scrolling. Instead of 200 list items for 200 tweets, I have only the visible items and a data list of 200 tweets. So far, it’s proving its worth. In recent testing, I’ve seen a 20mb memory reduction when using three accounts simultaneously in 2.0 compared to one account in 1.7.2. Results like this make me a happy developer.

DestroyTwitter 2.0 Preview: Preferences canvas

 

DestroyTwitter 2.0 Preview: Preferences canvas

Here’s a first glimpse at DestroyTwitter 2.0. Relating to yesterday’s post regarding preferences, I decided to segue into the Preferences canvas. In the current release, this canvas is a bit long-winded and claustrophobic. To combat these issues, I adopted an accordion layout where each set of preferences is expandable and collapsible. Not only does this clean up things a bit, but it also allows me to reuse components, which reduces memory. And finding new ways to reduce memory is like Christmas come early.

DestroyTwitter 2.0 Preview: Differentiating between preferences

 

DT2 Preferences

With multiple accounts in DestroyTwitter 2.0, there are a number of things to consider—one being preferences. In the current release, preferences are global across all accounts. In 2.0, however, some preferences are specific to each account. Above is my plan for divvying them up. Most rest under application, constant across all accounts, but there are a few that might differ from account to account.

Settings like refresh intervals are a given since some accounts require more frequent updates than others while some have canvases that don’t even need to update at all. The ability to disable Quick Friend Lookup when typing “@” is a new preference that is specific to each account. For instance, I want this enabled for my @destroytoday account since I frequently tweet to friends. On the reverse, I want it disabled for @destroytwitter since I only tweet to someone who asks a question or submits feedback.

Over the next few weeks, I’ll write a post previewing the upcoming release. I’m hoping to get it out this month, for private testing at the very least. Check back often for insight into what to expect.

DestroyTwitter on UserVoice

 

UserVoice

I recently signed up with UserVoice to handle feature requests and bug reports. I’m going to give it a shot and see how things go. After answering hundreds of emails each month, I realized many of them pertain to similar suggestions. UserVoice allows users to easily vote for features they want and see status on those already submitted. The service recently provided a year free for Twitter app developers, so why not? Check it out, go wild with requests, and I’ll do my best to stay active on it.

Poll: Replies vs Mentions

 

Replies vs Mentions

In implementing Lists as a replacement for Groups, the navigation changes a bit. I’m wondering if I should finally give in to the official Twitter nomenclature and replace “Replies” with “Mentions.” There are pros and cons to both. “Replies” isn’t entirely correct since you can be mentioned in a tweet and it not be a response. “Mentions” institutes another eight-letter “M” word, which could be confusing at quick glance. “Replies” widens the gap between the two sides and “Mentions” makes it a bit even. What do you think? Comment your reasoning.

The Twitter Lists API, reverse engineered

 

After asking for access to the Twitter Lists API a number of times, I was first turned down and now ignored. Since DestroyTwitter 2.0 is starting to take shape, I want to make sure it includes Lists, but the lack of API access is a big roadblock. Since Twitter stopped listening, I decided to do a bit of reverse engineering to find out what the available methods are. Here is what I found:

https://twitter.com/[username]/lists.xml

This method returns an array of the specified user’s lists. It includes details about each list (id, name, subscribers, etc) and alsothe return values for the user who created this list. The method utilizes the new cursor format for paging. Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="UTF-8"?>
<lists_list>
<lists type="array">
<list>
  <id>640968</id>
  <name>Developers</name>
  <full_name>@destroytoday/developers</full_name>
  <slug>developers</slug>
  <subscriber_count>0</subscriber_count>
  <member_count>22</member_count>
  <uri>/destroytoday/developers</uri>
  <mode>private</mode>
  <user>
    <id>14839458</id>
    <name>Jonnie Hallman</name>
    <screen_name>destroytoday</screen_name>
    <location>Baltimore, MD</location>
    <description>Founder of Destroy Today. Developer of DestroyFlickr and DestroyTwitter. Experience Designer at Adobe.</description>
    <profile_image_url>http://a3.twimg.com/profile_images/124402983/about-1_normal.jpg</profile_image_url>
    <url>http://www.destroytoday.com</url>
    <protected>false</protected>
    <followers_count>3328</followers_count>
    <profile_background_color>222222</profile_background_color>
    <profile_text_color>AAADB6</profile_text_color>
    <profile_link_color>00728F</profile_link_color>
    <profile_sidebar_fill_color>222222</profile_sidebar_fill_color>
    <profile_sidebar_border_color>222222</profile_sidebar_border_color>
    <friends_count>129</friends_count>
    <created_at>Tue May 20 00:08:47 +0000 2008</created_at>
    <favourites_count>122</favourites_count>
    <utc_offset>-18000</utc_offset>
    <time_zone>Eastern Time (US &amp; Canada)</time_zone>
    <profile_background_image_url>http://s.twimg.com/a/1257191498/images/themes/theme1/bg.png</profile_background_image_url>
    <profile_background_tile>false</profile_background_tile>
    <statuses_count>12148</statuses_count>
    <notifications>false</notifications>
    <geo_enabled>false</geo_enabled>
    <verified>false</verified>
    <following>false</following>
  </user>
</list>
</lists>
<next_cursor>0</next_cursor>
<previous_cursor>0</previous_cursor>
</lists_list>


https://twitter.com/[username]/lists/subscriptions.xml

This method is the same setup as the previous method in that it returns an array of lists, but this is for lists that the user is following besides his own. Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?xml version="1.0" encoding="UTF-8"?>
<lists_list>
<lists type="array">
<list>
  <id>1142867</id>
  <name>Flash devs not to follow</name>
  <full_name>@TheFlashBum/flash-devs-not-to-follow</full_name>
  <slug>flash-devs-not-to-follow</slug>
  <subscriber_count>1</subscriber_count>
 
  <member_count>8</member_count>
  <uri>/TheFlashBum/flash-devs-not-to-follow</uri>
  <mode>public</mode>
  <user>
    <id>26755983</id>
    <name>Jesse Freeman</name>
 
    <screen_name>TheFlashBum</screen_name>
    <location>NYC</location>
    <description>I am a homeless Flash Developer</description>
    <profile_image_url>http://a1.twimg.com/profile_images/475933036/flashbum_bio_normal.jpg</profile_image_url>
    <url>http://flashbum.com</url>
    <protected>false</protected>
 
    <followers_count>1146</followers_count>
    <profile_background_color>1A1B1F</profile_background_color>
    <profile_text_color>666666</profile_text_color>
    <profile_link_color>2FC2EF</profile_link_color>
    <profile_sidebar_fill_color>252429</profile_sidebar_fill_color>
    <profile_sidebar_border_color>181A1E</profile_sidebar_border_color>
 
    <friends_count>198</friends_count>
    <created_at>Thu Mar 26 14:07:20 +0000 2009</created_at>
    <favourites_count>140</favourites_count>
    <utc_offset>-18000</utc_offset>
    <time_zone>Eastern Time (US &amp; Canada)</time_zone>
 
    <profile_background_image_url>http://s.twimg.com/a/1257210731/images/themes/theme9/bg.gif</profile_background_image_url>
    <profile_background_tile>false</profile_background_tile>
    <statuses_count>4186</statuses_count>
    <notifications>false</notifications>
    <geo_enabled>false</geo_enabled>
    <verified>false</verified>
 
    <following>true</following>
  </user>
</list>
</lists>
<next_cursor>0</next_cursor>
<previous_cursor>0</previous_cursor>
</lists_list>


https://twitter.com/[username]/lists/[listname]/statuses.xml

This is the statuses method for a specific list. This is the same as any other statuses method. It includes the count, page, max_id, and since_id parameters. I haven’t tested for any others than those. Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
<status>
  <created_at>Tue Nov 03 23:35:59 +0000 2009</created_at>
  <id>5405262473</id>
  <text>AT&amp;T complaining about Verizon ad accurately portraying the piss-poor suckiness of their service in 98% of the country: http://bit.ly/e4ieH</text>
  <source>&lt;a href=&quot;http://destroytwitter.com/&quot; rel=&quot;nofollow&quot;&gt;DestroyTwitter&lt;/a&gt;</source>
  <truncated>false</truncated>
  <in_reply_to_status_id></in_reply_to_status_id>
  <in_reply_to_user_id></in_reply_to_user_id>
  <favorited>false</favorited>
  <in_reply_to_screen_name></in_reply_to_screen_name>
  <user>
    <id>14551527</id>
    <name>rjowen</name>
    <screen_name>rjowen</screen_name>
    <location>Denver, CO</location>
    <description></description>
    <profile_image_url>http://a3.twimg.com/profile_images/494903369/twitter_normal.jpg</profile_image_url>
    <url>http://rjria.blogspot.com</url>
    <protected>false</protected>
    <followers_count>318</followers_count>
    <profile_background_color>1A1B1F</profile_background_color>
    <profile_text_color>666666</profile_text_color>
    <profile_link_color>2FC2EF</profile_link_color>
    <profile_sidebar_fill_color>252429</profile_sidebar_fill_color>
    <profile_sidebar_border_color>181A1E</profile_sidebar_border_color>
    <friends_count>176</friends_count>
    <created_at>Sat Apr 26 21:06:42 +0000 2008</created_at>
    <favourites_count>8</favourites_count>
    <utc_offset>-25200</utc_offset>
    <time_zone>Mountain Time (US &amp; Canada)</time_zone>
    <profile_background_image_url>http://a1.twimg.com/profile_background_images/4099338/IMG_0224.JPG</profile_background_image_url>
    <profile_background_tile>false</profile_background_tile>
    <statuses_count>1801</statuses_count>
    <notifications>false</notifications>
    <geo_enabled>false</geo_enabled>
    <verified>false</verified>
    <following>false</following>
  </user>
  <geo/>
</status>


https://twitter.com/[username]/lists/[listname]/members.xml

Lastly, the method for adding a user to a list. It looks like a user can be added simply by including the user’s id in the id parameter. The result is the same as lists.xml. This method in particular requires the POST method. Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<list>
  <id>640968</id>
  <name>Developers</name>
  <full_name>@destroytoday/developers</full_name>
  <slug>developers</slug>
  <subscriber_count>0</subscriber_count>
  <member_count>22</member_count>
  <uri>/destroytoday/developers</uri>
  <mode>private</mode>
  <user>
    <id>14839458</id>
    <name>Jonnie Hallman</name>
    <screen_name>destroytoday</screen_name>
    <location>Baltimore, MD</location>
    <description>Founder of Destroy Today. Developer of DestroyFlickr and DestroyTwitter. Experience Designer at Adobe.</description>
    <profile_image_url>http://a3.twimg.com/profile_images/124402983/about-1_normal.jpg</profile_image_url>
    <url>http://www.destroytoday.com</url>
    <protected>false</protected>
    <followers_count>3330</followers_count>
    <profile_background_color>222222</profile_background_color>
    <profile_text_color>AAADB6</profile_text_color>
    <profile_link_color>00728F</profile_link_color>
    <profile_sidebar_fill_color>222222</profile_sidebar_fill_color>
    <profile_sidebar_border_color>222222</profile_sidebar_border_color>
    <friends_count>129</friends_count>
    <created_at>Tue May 20 00:08:47 +0000 2008</created_at>
    <favourites_count>122</favourites_count>
    <utc_offset>-18000</utc_offset>
    <time_zone>Eastern Time (US &amp; Canada)</time_zone>
    <profile_background_image_url>http://s.twimg.com/a/1257288876/images/themes/theme1/bg.png</profile_background_image_url>
    <profile_background_tile>false</profile_background_tile>
    <statuses_count>12159</statuses_count>
    <notifications>false</notifications>
    <geo_enabled>false</geo_enabled>
    <verified>false</verified>
    <following>false</following>
  </user>
</list>

In conclusion, I must say it’s pretty sad that an experienced Twitter developer needs to dig through the accepted developers’ work to be able to use the same features. I know Lists are new, but DestroyTwitter has been out far longer than the app I got this from. Also, the retweet and geo-location APIs have been public for months now, though the features aren’t completely implemented. DestroyTwitter doesn’t have millions of dollars worth of funding to bring to the table, but it has a pretty passionate developer who wants the latest features for his users.

[update] Thanks to Lim CHee Aun, who referred me to the draft documentation for Lists. It reveals a few more methods, including the creation and destruction of lists. The API URLs differ quite a bit from the Twitter API, using POST, DELETE, and GET with the same URL to create, delete, and return a list respectively. In the Twitter API, create and destroy are included in the URL itself. I’m hoping for the ability to bulk add/remove users to/from lists. That would seriously alleviate pressure from the hundreds of calls it would take otherwise.

Scrolling techniques—pick your poison

 

I’m always looking for ways to improve performance in my apps. One aspect that has always been in the back of my mind, but never implemented, is an improved scrolling technique.

Note: For the examples below, I used movies from my Netflix queue as dummy content. For demonstration purposes, the scrollRect and smooth recycling methods are not masked in these examples. Also, the names “Rigid Recycling” and “Smooth Recycling” are simply names I have given the following scrolling techniques—they are most likely not the correct terms.

scrollRect

At the moment, DestroyTwitter uses scrollRect—a property that masks a DisplayObject based on a set Rectangle. Each item in the data list is created, whether it’s visible or not. The upside to this method is scrolling speed—everything is rendered only once. The downside is memory usage—since there’s no recycling, every asset and object within each item is accounted for.

1
2
3
4
5
// bounds:Rectangle = new Rectangle (0, 0, _Area.width, _Area.height);
 
bounds.y = (_Content.height - _Area.height) * _Scroller.value;
 
_Content.scrollRect = bounds;

Rigid Recycling

An alternative is rigid recycling, which consists of only the visible items while the content of each is changed as the user scrolls. This is nice on the memory front, but smooth scrolling is a quality I just can’t strip from DestroyTwitter.

1
2
3
4
5
6
7
8
9
10
11
12
13
$itemHeight = 50;
$visibleItems = Math.round (_Area.height / $itemHeight);
$invisibleItems = _data.length - $visibleItems;
 
$A = _items.length;
 
for ($a = 0; $a < $A; $a++) {
	$Item = _items[$a];
 
	$n = Math.round ($invisibleItems * _Scroller.value) + $a;
 
	$Item.text = _data[$n];
}

Smooth Recycling

Along with rigid recycling is smooth recycling. There are only the visible items with two others for padding. As the user scrolls, the content moves much like the pure scrollRect technique. When the content’s y-coordinate is past a certain point, however, each item changes content and the list resets its position. This keeps memory low and covers the pixel-based scrolling, allowing the same effect that scrollRect has, but on a much lighter scale.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$itemHeight = 50;
$visibleItems = Math.round (_Area.height / $itemHeight);
$invisibleItems = _data.length - $visibleItems;
 
$n = Math.round ($invisibleItems * _Scroller.value);
 
bounds.y = $itemHeight * (1 + ($invisibleItems * _Scroller.value) - $n);
 
_Content.scrollRect = bounds;
 
$A = _items.length;
 
for ($a = 0; $a < $A; $a++) {
	$Item = _items[$a];
 
	$n = Math.round ($invisibleItems * _scroller.value) + $a - 1;
 
	$Item.text = ($n > -1 && $n < _data.length) ? _data[$n] : "";
}

One of the downsides to these recycling methods is rendering. Since you’re changing the content of each item whenever you scroll, AIR has to re-render everything that changes in the item. In DestroyTwitter’s case, this would be the bitmap icon, tweet text, and tweet background graphic. The scrollRect method renders once, then masks the list without having to render again. Though I’m not entirely sure how significant the speed hit would be, I’m guessing it won’t be too much since I cache user icons.

Another downside is that the item heights need to remain the same. The equation that makes them work requires a single value for the item height. This is a problem, since DestroyTwitter’s tweet heights vary based on the length of the tweet. I could standardize them, but I have a feeling it would look atrocious. Imagine a one-line tweet atop a five-line tweet. Since the item heights would have to be the max, that one-line tweet would have about 48 pixels of empty space.

What I want is a technique that might not be possible using a simple equation. It would require pre-processing to record the heights, then run on an equation that needs to reference the topmost tweet along with its height and ratio to the rest, and somehow scroll it smoothly with no hiccups. I think I consulted just about every developer I know to try to solve this, but it seems there is no simple way to go about it. I’ll keep hacking away, but if anyone has any suggestions, feel free to make an attempt.