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.

Sorry, there are no polls available at the moment.

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.

Tutorial: Context menus are our friends if we use them correctly

 

In my quest to blur the line between native apps and AIR apps, I decided to fully incorporate context menus (right-click) in the next release of DestroyTwitter. In my initial tests, I came to realize that the ContextMenu class is the roommate who is really nice and optimistic before you move in, then a nightmare to get along with once you’re finally settled. It screams to be extended, but ironically it’s a final class (not extendable). After a few hours, I found a solution. But first, the problem.

Let’s say I have 100 icons. I want each icon to have five actions accessible from its context menu. This results in 100 ContextMenu instances and 500 NativeMenuItem instances, as seen below:

ContextMenu memory

This is not pretty. The solution: share. In programming, sharing is one big step towards drastically reducing memory usage. If I share a single context menu, I will have one instance instead of 100 and five item instances instead of 500.

Context Menu memory

As expected, memory usage is divided by 100. The problem with sharing the ContextMenu class using a display object’s contextMenu property is that it can’t be done. On paper it can be, but not the way you want. To share a context menu, you’d have to listen on Event.DISPLAYING and use the hitTest() method to check which object the user is right-clicking. From a developer’s standpoint, this is simply unethical. Also, Flash content doesn’t update the mouse position between sequential right-clicks, so you could have a major issue with power users.

The solution is to forget the ContextMenu class altogether. While you’re at it, forget the contextMenu property as well. If you want something done right, you have to do it yourself. Add a listener to the MouseEvent.RIGHT_CLICK event, share a single NativeMenu instance, and call the NativeMenu.display() method in the right-click handler. For the purpose of this demonstration, I construct the NativeMenu in the Icon class. Since we’d ideally share the menu, you’d construct it outside of the class that’s using it, then reference 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
package {
	// import classes
 
	public class Icon extends Sprite {
		public var _menu:NativeMenu;
 
		public function Icon () {
			__construct ();
			__init ();
		}
		private function __construct ():void {
			_menu = new NativeMenu ();
 
			// construct NativeMenu items
		}
		private function __init ():void {
			addEventListener (MouseEvent.RIGHT_CLICK, __rightClick__, false, 0, true);
		}
		private function __rightClick__ ($event:MouseEvent):void {
			// change NativeMenu items' attributes
 
			_menu.display (stage, stage.mouseX, stage.mouseY);
		}
	}
}

There is a second chapter to the context menu nightmare, which is the API for managing items. It’s clumsy, excessive, and requires the developers to write some pretty gross-looking code. I’ll save the solution to that for another time.

DestroyTwitter + Twitter = @destroytwitter

 

DestroyTwitter twitter account

DestroyTwitter is finally on Twitter. For the past nine months, I’ve used my personal Twitter account (@destroytoday) to post news and respond to feedback. That’s all fine by me, but I figured an account of its own would make things more official. Lots of people asked how I was able to get an account with the word “Twitter” in the name. In all honesty, I simply asked. Since DestroyTwitter has been around for a while (for a Twitter app), they were able to make an exception. If you haven’t done so already, go ahead and follow the @destroytwitter account—and keep an eye out over the next few weeks.